{"id":3042,"date":"2026-09-25T13:16:43","date_gmt":"2026-09-25T07:46:43","guid":{"rendered":"https:\/\/www.smsgatewaycenter.com\/blog\/?p=3042"},"modified":"2026-09-25T13:19:49","modified_gmt":"2026-09-25T07:49:49","slug":"sender-identity-sms-whatsapp-rcs-telegram","status":"publish","type":"post","link":"https:\/\/www.smsgatewaycenter.com\/blog\/sender-identity-sms-whatsapp-rcs-telegram\/","title":{"rendered":"Sender Identity Across SMS, WhatsApp, RCS and Telegram: Sender IDs, WABA Numbers, Bots and Long Codes"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">A message on <strong>SMSGatewayCenter<\/strong> can come from five different kinds of sender: a DLT sender ID, a WhatsApp Business number, an <a href=\"https:\/\/www.smsgatewaycenter.com\/rcs-messaging\/\">RCS bot<\/a>, the account&#8217;s single <a href=\"https:\/\/www.smsgatewaycenter.com\/telegram-bulk-messaging\/\">Telegram bot<\/a>, or a long code plus keyword on the way in. Each has its own API, its own spelling in responses, its own JSON type and its own approval model. This is the field-level map and the registry design that ties them together.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/wp-content\/uploads\/2026\/09\/sender-identity-four-channels-featured.webp\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"584\" src=\"https:\/\/www.smsgatewaycenter.com\/blog\/wp-content\/uploads\/2026\/09\/sender-identity-four-channels-featured-1024x584.webp\" alt=\"Five abstract sender shapes sending signal lines into one registry, representing SMS sender IDs, WhatsApp numbers, RCS bots, a Telegram bot and long codes\" class=\"wp-image-3047\" srcset=\"https:\/\/www.smsgatewaycenter.com\/blog\/wp-content\/uploads\/2026\/09\/sender-identity-four-channels-featured-1024x584.webp 1024w, https:\/\/www.smsgatewaycenter.com\/blog\/wp-content\/uploads\/2026\/09\/sender-identity-four-channels-featured-300x171.webp 300w, https:\/\/www.smsgatewaycenter.com\/blog\/wp-content\/uploads\/2026\/09\/sender-identity-four-channels-featured-768x438.webp 768w, https:\/\/www.smsgatewaycenter.com\/blog\/wp-content\/uploads\/2026\/09\/sender-identity-four-channels-featured.webp 1200w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption class=\"wp-element-caption\">Five kinds of sender, one registry: the key you store decides whether a delivery report can be traced back to the brand that sent it.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Table of Contents<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><a href=\"#the-short-answer\">The Short Answer<\/a><\/li>\n\n\n\n<li><a href=\"#tldr\">TL;DR<\/a><\/li>\n\n\n\n<li><a href=\"#at-a-glance\">Five Sender Units at a Glance<\/a><\/li>\n\n\n\n<li><a href=\"#who-approves\">Who Issues, Who Approves, Who Can Change It<\/a><\/li>\n\n\n\n<li><a href=\"#sms-sender-ids\">SMS Sender IDs: A Mirror of the DLT Header<\/a><\/li>\n\n\n\n<li><a href=\"#reading-sender-list\">Reading the Sender ID List<\/a><\/li>\n\n\n\n<li><a href=\"#sender-template-pairing\">Sender ID and Template Pairing<\/a><\/li>\n\n\n\n<li><a href=\"#waba-number\">WhatsApp: The WABA Number Is Sender and Scope<\/a><\/li>\n\n\n\n<li><a href=\"#rcs-bots\">RCS: The Bot Is the Sender<\/a><\/li>\n\n\n\n<li><a href=\"#telegram-bot\">Telegram: One Bot per Account<\/a><\/li>\n\n\n\n<li><a href=\"#long-code-keyword\">Inbound SMS: Long Code Plus Keyword<\/a><\/li>\n\n\n\n<li><a href=\"#many-spellings\">One Sender, Many Spellings<\/a><\/li>\n\n\n\n<li><a href=\"#scope-matrix\">Which Calls Each Sender Unit Scopes<\/a><\/li>\n\n\n\n<li><a href=\"#registry-schema\">A Sender Registry Schema<\/a><\/li>\n\n\n\n<li><a href=\"#resolving-sender\">Resolving the Sender on Every Report and Inbox Row<\/a><\/li>\n\n\n\n<li><a href=\"#code-samples\">Four Code Samples, Four Traps<\/a><\/li>\n\n\n\n<li><a href=\"#sender-lifecycle\">Renaming, Retiring and Replacing a Sender<\/a><\/li>\n\n\n\n<li><a href=\"#ten-mistakes\">Ten Mistakes That Break Sender Attribution<\/a><\/li>\n\n\n\n<li><a href=\"#decision-matrix\">Decision Matrix<\/a><\/li>\n\n\n\n<li><a href=\"#checklist\">Pre-Launch Checklist<\/a><\/li>\n\n\n\n<li><a href=\"#unspecified-behaviour\">Unspecified Behaviour and How to Code Around It<\/a><\/li>\n\n\n\n<li><a href=\"#faqs\">FAQs<\/a><\/li>\n<\/ol>\n\n\n\n<h2 id=\"the-short-answer\" class=\"wp-block-heading\">The Short Answer<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Every channel on <strong>SMSGatewayCenter<\/strong> answers &#8220;who sent this?&#8221; with a different object. <a href=\"https:\/\/www.smsgatewaycenter.com\/transactional-sms\/\">SMS<\/a> uses a sender ID (the DLT header), managed through <code>SMSApi\/senderid\/create<\/code>, <code>read<\/code>, <code>update<\/code> and <code>delete<\/code>, sent as <code>senderid<\/code> and reported back as <code>senderName<\/code>. WhatsApp uses the WhatsApp Business number, <code>wabaNumber<\/code>, which is both the sender and the scope for templates, media, reports and inbox reads. RCS uses a bot, addressed by an integer <code>botId<\/code> on send but reported by <code>botName<\/code> on delivery rows. Telegram uses the single bot attached to the account, so the send call carries no sender parameter at all. Inbound SMS adds a fifth unit: the long code plus keyword pair that arrives as <code>phonecode<\/code> and <code>keyword<\/code> on the push.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The same sender appears under different names and JSON types across endpoints: the <a href=\"https:\/\/www.smsgatewaycenter.com\/whatsapp-business-api\/\">WhatsApp number<\/a> is an unquoted number on report rows, a quoted string on inbox rows, and <code>waNumber<\/code> on analytics rows. The robust design is a sender registry in your own database with a surrogate key per sender, the channel identity stored as text, and a resolver that maps every report and inbox row back to that key.<\/p>\n\n\n\n<h2 id=\"tldr\" class=\"wp-block-heading\">TL;DR<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Five sender units:<\/strong> SMS sender ID, WhatsApp <code>wabaNumber<\/code>, RCS <code>botId<\/code>, the account&#8217;s Telegram bot, and the inbound long code plus <code>keyword<\/code>.<\/li>\n\n\n\n<li><strong>SMS sender IDs have a full CRUD API<\/strong> under <code>SMSApi\/senderid\/<\/code>. Create takes only <code>senderid<\/code> and returns no identifier; read returns <code>sId<\/code>, <code>senderName<\/code>, <code>isEnabled<\/code> and <code>addTime<\/code>, all as quoted strings.<\/li>\n\n\n\n<li><strong><code>isEnabled<\/code> is not a boolean.<\/strong> The sample value is the string <code>\"Pending\"<\/code>, and the list response counts senders in <code>total<\/code>, <code>pending<\/code>, <code>active<\/code> and <code>rejected<\/code> buckets.<\/li>\n\n\n\n<li><strong><code>count<\/code> on the sender list is an object,<\/strong> not a number. Do not share a paging helper with endpoints where <code>count<\/code> is an integer.<\/li>\n\n\n\n<li><strong>Sender and template must match on SMS.<\/strong> A mismatch fails the message with <code>SENDERID_MISMATCH<\/code>.<\/li>\n\n\n\n<li><strong>WhatsApp&#8217;s number changes type by endpoint:<\/strong> unquoted number on <code>WAApi\/report<\/code> rows, quoted string on inbox rows, and renamed <code>waNumber<\/code> on analytics rows. Store it as text and normalise before comparing.<\/li>\n\n\n\n<li><strong>RCS reports the sender by name, not id.<\/strong> Delivery rows carry <code>botName<\/code>; inbox rows carry an integer <code>botId<\/code>; <code>rest\/rcs\/v1\/bots<\/code> maps one to the other but returns active bots only.<\/li>\n\n\n\n<li><strong>Telegram has one bot per account.<\/strong> Nothing on a Telegram delivery row names the bot, and none needs to.<\/li>\n\n\n\n<li><strong>Design:<\/strong> a sender registry keyed on your own surrogate, one resolver per channel, and a daily snapshot of every sender list.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"at-a-glance\" class=\"wp-block-heading\">Five Sender Units at a Glance<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The table puts the sender side of five endpoint families next to each other. Each row maps to a page on the <a href=\"https:\/\/www.smsgatewaycenter.com\/developer-api\/\">SMSGatewayCenter developer API<\/a>, so any cell can be checked against its source.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Property<\/th><th>SMS sender ID<\/th><th>WhatsApp number<\/th><th>RCS bot<\/th><th>Telegram bot<\/th><th>Inbound long code<\/th><\/tr><\/thead><tbody><tr><td>What the recipient sees<\/td><td>Header such as <code>SMSGAT<\/code><\/td><td>Business profile of the number<\/td><td>Branded bot<\/td><td>Bot name and handle<\/td><td>Your long code (you are the recipient)<\/td><\/tr><tr><td>Parameter on send<\/td><td><code>senderid<\/code><\/td><td><code>wabaNumber<\/code><\/td><td><code>botId<\/code><\/td><td>None<\/td><td>Not applicable<\/td><\/tr><tr><td>Local identifier<\/td><td><code>sId<\/code> (quoted string)<\/td><td>The number itself<\/td><td><code>botId<\/code> (integer)<\/td><td>None exposed<\/td><td><code>phonecode<\/code> plus <code>keyword<\/code><\/td><\/tr><tr><td>Managed through API<\/td><td>Create, read, update, delete<\/td><td>Read-only use; no create<\/td><td>List only (<code>rest\/rcs\/v1\/bots<\/code>)<\/td><td>Setup <code>get<\/code> and <code>save<\/code><\/td><td>Portal<\/td><\/tr><tr><td>Approval signal<\/td><td><code>isEnabled<\/code> string, e.g. <code>\"Pending\"<\/code><\/td><td>Not exposed<\/td><td>Only active bots are listed<\/td><td>Not applicable<\/td><td>Not applicable<\/td><\/tr><tr><td>Name on delivery rows<\/td><td><code>senderName<\/code><\/td><td><code>wabaNumber<\/code> (unquoted number)<\/td><td><code>botName<\/code><\/td><td>Not present<\/td><td>Not applicable<\/td><\/tr><tr><td>Name on inbox rows<\/td><td>Not applicable<\/td><td><code>wabaNumber<\/code> (quoted string)<\/td><td><code>botId<\/code> (integer)<\/td><td>Not present<\/td><td><code>phonecode<\/code>, <code>keyword<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/wp-content\/uploads\/2026\/09\/diagram-five-sender-units.svg\"><img decoding=\"async\" src=\"https:\/\/www.smsgatewaycenter.com\/blog\/wp-content\/uploads\/2026\/09\/diagram-five-sender-units.svg\" alt=\"Five-column comparison of SMS sender ID, WhatsApp number, RCS bot, Telegram bot and inbound long code showing the send parameter, local identifier, approval signal and how each appears on reports\" class=\"wp-image-3048\"\/><\/a><figcaption class=\"wp-element-caption\">Five answers to &#8220;who sent this?&#8221;. No two columns use the same field name for the sender.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Read the last two rows first. Most attribution bugs start there: the field that identifies the sender on the way out is not the field that identifies it on the way back.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"who-approves\" class=\"wp-block-heading\">Who Issues, Who Approves, Who Can Change It<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Sender unit<\/th><th>Issued by<\/th><th>Approved by<\/th><th>Changeable through the API<\/th><\/tr><\/thead><tbody><tr><td>SMS sender ID<\/td><td>You register the header on an operator DLT portal<\/td><td>The DLT portal, then the platform record<\/td><td>Yes: create, update and delete under <code>SMSApi\/senderid\/<\/code><\/td><\/tr><tr><td>WhatsApp number<\/td><td>Provisioned onto your account<\/td><td>Meta and the platform during onboarding<\/td><td>No; you use it, you do not manage it through the developer API<\/td><\/tr><tr><td>RCS bot<\/td><td>Provisioned onto your account<\/td><td>Launched and activated before it appears in the bot list<\/td><td>No; list only<\/td><\/tr><tr><td>Telegram bot<\/td><td>You create it with BotFather and save its token<\/td><td>No approval step<\/td><td>Yes: <code>rest\/tg\/v1\/setup<\/code> with <code>save<\/code><\/td><\/tr><tr><td>Inbound long code and keyword<\/td><td>Allocated by plan; keyword configured in the portal<\/td><td>Not applicable<\/td><td>No<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Two practical consequences. First, only SMS sender IDs and the Telegram bot can be created from your own code, so any &#8220;add a sender&#8221; feature in your product covers those two and routes everything else to an onboarding workflow. Second, the Telegram bot is the only sender with no approval gate, so a wrong token is live the moment you save it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Telegram&#8217;s own <a href=\"https:\/\/core.telegram.org\/bots#botfather\" target=\"_blank\" rel=\"noopener nofollow\">BotFather documentation<\/a> covers creating the bot and its token; the platform side is the <code>setup<\/code> endpoint described in the <a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/telegram-messaging-api-chat-id-model\/\">Telegram messaging API guide<\/a>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"sms-sender-ids\" class=\"wp-block-heading\">SMS Sender IDs: A Mirror of the DLT Header<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In India, a <a href=\"https:\/\/www.smsgatewaycenter.com\">commercial SMS<\/a> header must be registered on an operator DLT portal before it can carry traffic. The platform&#8217;s sender ID record mirrors that registration: the update page describes the operation as a way to reflect changes &#8220;made or updated in DLT portals&#8221;. Register on DLT first; manage the platform copy through the API second. The <a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/dlt-registration-step-by-step-guide-india\/\">DLT registration guide<\/a> walks through the portal side.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The four endpoints<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Operation<\/th><th>Endpoint<\/th><th>Method<\/th><th>Parameters beyond auth and <code>output<\/code><\/th><th>Success <code>msg<\/code><\/th><\/tr><\/thead><tbody><tr><td>Create<\/td><td><code>https:\/\/unify.smsgateway.center\/SMSApi\/senderid\/create<\/code><\/td><td>POST (page also states GET)<\/td><td><code>senderid<\/code><\/td><td><code>SenderId created successfully.<\/code><\/td><\/tr><tr><td>Read<\/td><td><code>https:\/\/unify.smsgateway.center\/SMSApi\/senderid\/read<\/code><\/td><td>POST or GET<\/td><td>none<\/td><td><code>success<\/code><\/td><\/tr><tr><td>Update<\/td><td><code>https:\/\/unify.smsgateway.center\/SMSApi\/senderid\/update<\/code><\/td><td>POST only<\/td><td><code>senderid<\/code>, <code>id<\/code><\/td><td><code>SenderId updated successfully.<\/code><\/td><\/tr><tr><td>Delete<\/td><td><code>https:\/\/unify.smsgateway.center\/SMSApi\/senderid\/delete<\/code><\/td><td>POST only<\/td><td><code>id<\/code><\/td><td><code>Senderid deleted successfully<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">All four return <code>code<\/code> as the quoted string <code>\"200\"<\/code> nested under <code>response<\/code>, with <code>api<\/code> set to <code>\"senderid\"<\/code>. Note the capitalisation of the word in the <code>msg<\/code> strings drifts between operations, which is one more reason to branch on <code>status<\/code> and never on <code>msg<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Format rules to enforce before calling create<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The create page describes Indian sender IDs as alphanumeric, up to six characters. Enforce that in your own validator before the call: uppercase letters and digits, one to six characters. A header that passes a six-character check is valid under any stricter reading, and the check turns a portal rejection into an immediate form error.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import re\n\nSENDER_ID = re.compile(r\"^&#91;A-Z0-9]{1,6}$\")\n\ndef validate_sender_id(value: str) -&gt; str:\n    v = value.strip().upper()\n    if not SENDER_ID.fullmatch(v):\n        raise ValueError(\"sender ID must be 1 to 6 letters or digits\")\n    return v<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">What the create does not return<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The response to <code>create<\/code> confirms success and carries no <code>sId<\/code>. The only way to learn the identifier you need for <code>update<\/code> and <code>delete<\/code> is to read the list and match on <code>senderName<\/code>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"reading-sender-list\" class=\"wp-block-heading\">Reading the Sender ID List<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n  \"response\": {\n    \"api\": \"senderid\",\n    \"action\": \"read\",\n    \"status\": \"success\",\n    \"msg\": \"success\",\n    \"code\": \"200\",\n    \"count\": {\n      \"total\": 1,\n      \"pending\": 1,\n      \"active\": 0,\n      \"rejected\": 0\n    },\n    \"senderidList\": &#91;\n      {\n        \"senderid\": {\n          \"sId\": \"179\",\n          \"senderName\": \"TESTIN\",\n          \"isEnabled\": \"Pending\",\n          \"addTime\": \"1563033651005\"\n        }\n      }\n    ]\n  }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Five things in that response decide how you parse it.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong><code>count<\/code> is an object with four buckets.<\/strong> On the SMS delivery report <code>count<\/code> is also an object but with <code>total<\/code> and <code>current<\/code>; on the template read it is an integer; on account status it counts keys. A shared helper that reads <code>count<\/code> as a number will throw here.<\/li>\n\n\n\n<li><strong>Rows are double wrapped.<\/strong> Each element of <code>senderidList<\/code> holds a <code>senderid<\/code> object, so the name sits at <code>response.senderidList[i].senderid.senderName<\/code>. The word &#8220;senderid&#8221; is therefore a request parameter, a list prefix and a wrapper key all at once.<\/li>\n\n\n\n<li><strong><code>isEnabled<\/code> is a status string.<\/strong> Despite the name, the sample value is <code>\"Pending\"<\/code>, and the bucket names suggest <code>active<\/code> and <code>rejected<\/code> states as well. Treat it as an enumeration, not a boolean.<\/li>\n\n\n\n<li><strong>Everything is a quoted string,<\/strong> including <code>sId<\/code> and the epoch-millisecond <code>addTime<\/code>.<\/li>\n\n\n\n<li><strong>There is no paging.<\/strong> The whole list comes back in one response.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"sender-template-pairing\" class=\"wp-block-heading\">Sender ID and Template Pairing<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">On SMS, the sender ID and the template are registered together on DLT. The platform enforces the pair at send time: the knowledge base entry for <a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/kb\/what-does-senderid_mismatch-senderid-mismatch-with-template-mean\/\"><code>SENDERID_MISMATCH<\/code><\/a> states the message was not sent because the sender ID does not match the one the template is registered with.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The pairing is visible from the API. Each row of <code>SMSApi\/template\/read<\/code> carries <code>senderIds<\/code>, a single string despite its plural name. One approved template can be linked to several sender IDs, as the knowledge base entry on <a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/kb\/can-i-use-one-template-across-multiple-sender-ids\/\">using one template across multiple sender IDs<\/a> confirms, provided each mapping exists on DLT and in your account.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The pre-send check is therefore a set membership test, not an equality test:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>def allowed_senders(template_row: dict) -&gt; set&#91;str]:\n    raw = template_row.get(\"senderIds\", \"\") or \"\"\n    return {s.strip().upper() for s in raw.split(\",\") if s.strip()}\n\ndef check_pair(template_row: dict, senderid: str) -&gt; None:\n    if senderid.upper() not in allowed_senders(template_row):\n        raise ValueError(f\"{senderid} is not mapped to template {template_row.get('mtId')}\")<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Splitting on commas is defensive: the published sample shows one value, and a list with one element is still a list. The full template model, including <code>mtId<\/code> and <code>dltTemplateId<\/code>, is covered in <a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/message-template-management-four-channels\/\">message template management across four channels<\/a>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"waba-number\" class=\"wp-block-heading\">WhatsApp: The WABA Number Is Sender and Scope<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">On WhatsApp the sender is the WhatsApp Business number, passed as <code>wabaNumber<\/code>. It does more work than any other sender field on the platform, because it also scopes most WhatsApp operations:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Operation<\/th><th>How <code>wabaNumber<\/code> is used<\/th><\/tr><\/thead><tbody><tr><td><code>WAApi\/send<\/code><\/td><td>Required: the number the message comes from<\/td><\/tr><tr><td><code>WAApi\/template<\/code> read<\/td><td>Required: templates are listed per number<\/td><\/tr><tr><td><code>WAApi\/template<\/code> create<\/td><td>Required; delete identifies the template by name and <code>language<\/code><\/td><\/tr><tr><td><code>WAApi\/media<\/code> upload<\/td><td>Required; media reads are account-wide<\/td><\/tr><tr><td><code>WAApi\/report<\/code><\/td><td>Required filter; echoed on every row<\/td><\/tr><tr><td><code>rest\/wa\/v1\/analytics<\/code><\/td><td>Optional filter; <code>groupBy=waNumber<\/code> groups rows by number<\/td><\/tr><tr><td><code>rest\/wa\/v1\/inbox<\/code><\/td><td>Optional filter; present on every row<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The type of the value changes with the endpoint that returns it:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Where it appears<\/th><th>Field name<\/th><th>JSON type<\/th><th>Sample<\/th><\/tr><\/thead><tbody><tr><td><code>WAApi\/report<\/code> row<\/td><td><code>wabaNumber<\/code><\/td><td>Unquoted number<\/td><td><code>9170396xxxxx<\/code><\/td><\/tr><tr><td><code>rest\/wa\/v1\/inbox<\/code> row<\/td><td><code>wabaNumber<\/code><\/td><td>Quoted string<\/td><td><code>\"9170396xxxxx\"<\/code><\/td><\/tr><tr><td><code>rest\/wa\/v1\/analytics<\/code> delivery row<\/td><td><code>waNumber<\/code><\/td><td>Quoted string<\/td><td><code>\"9170396xxxxx\"<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">A twelve-digit Indian number fits safely in a JavaScript number, so the unquoted report value does not lose precision today. It still breaks equality: <code>9170396000001 === \"9170396000001\"<\/code> is false. Normalise every occurrence to a digit string at the parser boundary and compare strings only. The report row also emits <code>uuId<\/code> as an unquoted nineteen-digit number, which does lose precision; the <a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/whatsapp-business-api-wire-contract\/\">WhatsApp Business API wire contract<\/a> covers that trap and the lossless parsing fix.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To build the list of numbers that are actually carrying traffic, call <code>rest\/wa\/v1\/analytics<\/code> with <code>action=delivery<\/code> and <code>groupBy=waNumber<\/code> over a recent range. Each row names one number that sent messages in that window.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"rcs-bots\" class=\"wp-block-heading\">RCS: The Bot Is the Sender<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">On RCS the sender is a bot. The bot list endpoint is <code>GET https:\/\/unify.smsgateway.center\/rest\/rcs\/v1\/bots<\/code> (legacy alias <code>\/rest\/rcs\/bots<\/code>), with <code>userid<\/code>, <code>output=json<\/code> and either <code>password<\/code> or the <code>apikey<\/code> header. Its only action is <code>list<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n  \"status\": \"success\",\n  \"botsList\": &#91;\n    {\n      \"botId\": 3,\n      \"name\": \"YourBot\",\n      \"status\": 1,\n      \"logoUrl\": \"https:\/\/example.com\/bots\/logo.png\",\n      \"isSandbox\": 0\n    }\n  ],\n  \"totalRecords\": 1,\n  \"page\": 1,\n  \"limit\": 1,\n  \"fromDate\": \"\",\n  \"toDate\": \"\",\n  \"statusCode\": \"200\",\n  \"reason\": \"success\"\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The notes on the page state three rules: only active bots (<code>status<\/code> equal to <code>1<\/code>) are returned; use <code>botId<\/code> from this list in the send and template APIs; <code>isSandbox<\/code> of <code>1<\/code> marks a sandbox bot. On error, <code>botsList<\/code> becomes an empty array with a <code>403<\/code> and a reason such as &#8220;RCS API access is not enabled for this account.&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The complication is on the way back. The RCS delivery row identifies the sender by <code>botName<\/code>, a string, and carries no <code>botId<\/code>. The RCS inbox row identifies it by <code>botId<\/code>, an unquoted integer, and carries no name. The only bridge between the two is the bot list, and the bot list omits inactive bots. A delivery row from a bot that has since been deactivated cannot be mapped back through a live lookup. Snapshot the bot list daily into your registry, so a name that disappears from the live list still resolves from history.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Keep sandbox bots out of production routing by filtering on <code>isSandbox<\/code> at selection time, not by naming convention. The <a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/rcs-messaging-api-reference-migration-from-sms\/\">RCS messaging API reference<\/a> covers the send payload that consumes <code>botId<\/code>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"telegram-bot\" class=\"wp-block-heading\">Telegram: One Bot per Account<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Telegram is the simplest case because there is only one sender. The account holds a single bot, configured through <code>rest\/tg\/v1\/setup<\/code>: <code>get<\/code> (GET) returns the configuration; <code>save<\/code> (POST) accepts <code>botName<\/code>, <code>botHandle<\/code> and <code>botToken<\/code>. The token is write-only: it goes in and never comes back out on a read.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Three consequences:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>The send call has no sender parameter.<\/strong> <code>rest\/tg\/v1\/send<\/code> addresses recipients by <code>chatId<\/code> or <code>phoneNumber<\/code>; the bot is implied by the account.<\/li>\n\n\n\n<li><strong>Delivery and inbox rows carry no bot field.<\/strong> None is needed while the account holds exactly one bot, and your registry should record which bot handle was configured on which dates so historical rows stay attributable after a token rotation or a bot change.<\/li>\n\n\n\n<li><strong>Brands that need separate Telegram identities need separate accounts.<\/strong> Plan the account structure before you plan the bots.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Because the token cannot be read back, store it in your own secrets manager at the moment you save it, and treat a <code>save<\/code> as a deployment: a wrong token takes effect immediately and no approval step catches it.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Inbound SMS: Long Code Plus Keyword {#long-code-keyword}<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The fifth sender unit points the other way. When a customer replies to a long code or texts a keyword, the platform forwards the message to your URL as an HTTP GET with six query parameters: <code>phonecode<\/code>, <code>keyword<\/code>, <code>phoneno<\/code>, <code>content<\/code>, <code>location<\/code> and <code>carrier<\/code>. <code>phonecode<\/code> and <code>keyword<\/code> are optional in the published contract; <code>phoneno<\/code> is the customer&#8217;s number and <code>content<\/code> is the text.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For routing, the pair of long code and keyword is your inbound &#8220;sender unit&#8221;: it tells you which campaign, brand or workflow a reply belongs to. Keywords are configured in the portal, as described in <a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/create-new-keyword-two-way-sms-longcode\/\">creating a new keyword for two-way SMS<\/a>, and the full push contract is covered in <a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/receiving-messages-four-channel-inbox-contracts\/\">receiving messages on four channels<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Normalise both values before routing: uppercase the keyword, strip whitespace, and treat a missing <code>phonecode<\/code> as &#8220;the account&#8217;s default long code&#8221; only if you hold exactly one. With more than one long code and no <code>phonecode<\/code>, route on keyword alone and send unmatched messages to a review queue rather than guessing.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"many-spellings\" class=\"wp-block-heading\">One Sender, Many Spellings<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Channel<\/th><th>Where<\/th><th>Field name<\/th><th>JSON type<\/th><\/tr><\/thead><tbody><tr><td>SMS<\/td><td>Send request<\/td><td><code>senderid<\/code><\/td><td>Form field<\/td><\/tr><tr><td>SMS<\/td><td>Sender list row<\/td><td><code>senderName<\/code> (plus <code>sId<\/code>)<\/td><td>Quoted string<\/td><\/tr><tr><td>SMS<\/td><td>Sender list wrapper<\/td><td><code>senderidList[i].senderid<\/code><\/td><td>Object<\/td><\/tr><tr><td>SMS<\/td><td>Template row<\/td><td><code>senderIds<\/code><\/td><td>Quoted string, plural name<\/td><\/tr><tr><td>SMS<\/td><td>Delivery report row<\/td><td><code>senderName<\/code><\/td><td>Quoted string<\/td><\/tr><tr><td>SMS<\/td><td>Delivery summary<\/td><td><code>groupby=senderid<\/code><\/td><td>Request value<\/td><\/tr><tr><td>WhatsApp<\/td><td>Send, template, media, report requests<\/td><td><code>wabaNumber<\/code><\/td><td>Form field<\/td><\/tr><tr><td>WhatsApp<\/td><td>Report row<\/td><td><code>wabaNumber<\/code><\/td><td>Unquoted number<\/td><\/tr><tr><td>WhatsApp<\/td><td>Inbox row<\/td><td><code>wabaNumber<\/code><\/td><td>Quoted string<\/td><\/tr><tr><td>WhatsApp<\/td><td>Analytics row<\/td><td><code>waNumber<\/code><\/td><td>Quoted string<\/td><\/tr><tr><td>RCS<\/td><td>Send and template list requests<\/td><td><code>botId<\/code><\/td><td>Form or query field<\/td><\/tr><tr><td>RCS<\/td><td>Bot list row<\/td><td><code>botId<\/code> and <code>name<\/code><\/td><td>Integer and string<\/td><\/tr><tr><td>RCS<\/td><td>Template list row<\/td><td><code>botName<\/code><\/td><td>Quoted string<\/td><\/tr><tr><td>RCS<\/td><td>Delivery report row<\/td><td><code>botName<\/code><\/td><td>Quoted string<\/td><\/tr><tr><td>RCS<\/td><td>Inbox row<\/td><td><code>botId<\/code><\/td><td>Unquoted integer<\/td><\/tr><tr><td>Telegram<\/td><td>Setup<\/td><td><code>botName<\/code>, <code>botHandle<\/code>, <code>botToken<\/code><\/td><td>Strings; token write-only<\/td><\/tr><tr><td>Inbound SMS<\/td><td>Push query string<\/td><td><code>phonecode<\/code>, <code>keyword<\/code><\/td><td>Query parameters<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Seventeen places, more than ten spellings, and at least four JSON representations. The fix is not to memorise the table but to encode it once: one small resolver per channel that reads the right field at the right nesting level, converts it to your canonical form, and returns your registry key.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"scope-matrix\" class=\"wp-block-heading\">Which Calls Each Sender Unit Scopes<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Operation<\/th><th>SMS sender ID<\/th><th>WhatsApp number<\/th><th>RCS bot<\/th><th>Telegram bot<\/th><\/tr><\/thead><tbody><tr><td>Send<\/td><td>Required (<code>senderid<\/code>)<\/td><td>Required<\/td><td>Required<\/td><td>Implied<\/td><\/tr><tr><td>Template list<\/td><td>Not a filter; pairing visible as <code>senderIds<\/code><\/td><td>Required<\/td><td>Required<\/td><td>Account-wide<\/td><\/tr><tr><td>Template create<\/td><td>Not a parameter<\/td><td>Required<\/td><td>Inside <code>templateData<\/code><\/td><td>Portal only<\/td><\/tr><tr><td>Media upload<\/td><td>Not applicable<\/td><td>Required<\/td><td>Not applicable<\/td><td>Not applicable<\/td><\/tr><tr><td>Delivery report query<\/td><td>Not among the query filters; echoed as <code>senderName<\/code> on each row<\/td><td>Required<\/td><td>Echoed as <code>botName<\/code> on each row<\/td><td>Account-wide<\/td><\/tr><tr><td>Summary or analytics<\/td><td><code>groupby=senderid<\/code> on the SMS summary<\/td><td>Filter and <code>groupBy=waNumber<\/code><\/td><td>Group delivery rows by <code>botName<\/code> yourself<\/td><td>Account-wide<\/td><\/tr><tr><td>Inbox<\/td><td>Not applicable (push)<\/td><td>Optional filter<\/td><td>Echoed as <code>botId<\/code><\/td><td>Account-wide<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The asymmetry to design around: WhatsApp report queries require a number, so a job that reconciles all WhatsApp traffic has to loop over every number you hold. The SMS report query is account-wide and carries the sender on each row, so the same job makes one pass and groups afterwards. If your list of WhatsApp numbers is incomplete, the WhatsApp side of that job is silently incomplete too; the analytics <code>groupBy=waNumber<\/code> call described above is how you detect it.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"registry-schema\" class=\"wp-block-heading\">A Sender Registry Schema<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The registry is the table every send path reads to pick a sender and every ingestion path reads to attribute a row. PostgreSQL shown; the decisions carry to any relational store.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CREATE TABLE sender (\n    sender_key        TEXT PRIMARY KEY,        -- your stable key, e.g. 'acme_alerts_sms'\n    channel           TEXT NOT NULL CHECK (channel IN ('sms','whatsapp','rcs','telegram','sms_inbound')),\n    brand             TEXT NOT NULL,           -- the business unit this sender speaks for\n\n    -- channel identity, stored as text exactly as normalised\n    sms_sender_id     TEXT,                    -- 'SMSGAT', uppercase\n    sms_s_id          TEXT,                    -- sId, quoted string from the list\n    waba_number       TEXT,                    -- digits only, never numeric\n    rcs_bot_id        TEXT,                    -- integer stored as text\n    rcs_bot_name      TEXT,                    -- the name delivery rows carry\n    tg_bot_handle     TEXT,\n    inbound_long_code TEXT,\n    inbound_keyword   TEXT,                    -- uppercase\n\n    -- state\n    raw_status        TEXT,                    -- isEnabled, bot status, etc., verbatim\n    is_sandbox        BOOLEAN NOT NULL DEFAULT FALSE,\n    usable            BOOLEAN NOT NULL DEFAULT FALSE,   -- derived by the sync job only\n    valid_from        TIMESTAMPTZ NOT NULL DEFAULT now(),\n    valid_to          TIMESTAMPTZ,                      -- set when retired, never delete\n    last_seen_at      TIMESTAMPTZ\n);\n\nCREATE UNIQUE INDEX ux_sms_sender  ON sender (sms_sender_id)  WHERE channel = 'sms' AND valid_to IS NULL;\nCREATE UNIQUE INDEX ux_waba        ON sender (waba_number)    WHERE channel = 'whatsapp' AND valid_to IS NULL;\nCREATE UNIQUE INDEX ux_rcs_bot     ON sender (rcs_bot_id)     WHERE channel = 'rcs' AND valid_to IS NULL;\nCREATE INDEX        ix_rcs_name    ON sender (rcs_bot_name)   WHERE channel = 'rcs';\nCREATE UNIQUE INDEX ux_inbound     ON sender (inbound_long_code, inbound_keyword) WHERE channel = 'sms_inbound' AND valid_to IS NULL;\n\nCREATE TABLE sender_template (          -- SMS pairing, mirrored from template senderIds\n    sender_key   TEXT REFERENCES sender(sender_key),\n    template_key TEXT NOT NULL,\n    PRIMARY KEY (sender_key, template_key)\n);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Six decisions, and why:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Surrogate key plus brand.<\/strong> Attribution questions are asked in business terms (&#8220;what did the alerts brand send?&#8221;), and a brand can own senders on several channels.<\/li>\n\n\n\n<li><strong>Every identifier is text.<\/strong> <code>sId<\/code> arrives quoted, <code>wabaNumber<\/code> arrives both quoted and unquoted, and <code>botId<\/code> is an integer on one endpoint and absent on another. Text columns absorb all of it without loss.<\/li>\n\n\n\n<li><strong>Validity intervals, not deletion.<\/strong> Delivery rows arrive for weeks after a send. A retired sender must still resolve, so rows are closed with <code>valid_to<\/code>, never deleted.<\/li>\n\n\n\n<li><strong><code>rcs_bot_name<\/code> is indexed but not unique.<\/strong> RCS delivery rows resolve by name. If a name is ever reused after a bot is replaced, the validity interval disambiguates by time.<\/li>\n\n\n\n<li><strong><code>usable<\/code> is derived.<\/strong> Only the sync job sets it, from the live lists: sender ID <code>isEnabled<\/code> indicating active, bot present in <code>botsList<\/code> with <code>isSandbox<\/code> matching the environment.<\/li>\n\n\n\n<li><strong>The SMS pairing lives in its own table.<\/strong> It mirrors <code>senderIds<\/code> from the template read and backs the pre-send pair check.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">The outbound side of this design is the <a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/outbound-message-table-schema-design\/\">outbound message table<\/a>: store <code>sender_key<\/code> on every outbound row at submit time, and attribution never depends on a report field at all for messages you sent yourself.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"resolving-sender\" class=\"wp-block-heading\">Resolving the Sender on Every Report and Inbox Row<\/h2>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/wp-content\/uploads\/2026\/09\/diagram-sender-resolution-flow.svg\"><img decoding=\"async\" src=\"https:\/\/www.smsgatewaycenter.com\/blog\/wp-content\/uploads\/2026\/09\/diagram-sender-resolution-flow.svg\" alt=\"Flow from report and inbox rows on four channels, through a per-channel field extractor and normaliser, to a registry lookup by channel identity and time\" class=\"wp-image-3049\"\/><\/a><figcaption class=\"wp-element-caption\">Each channel names the sender differently on the way back. One resolver per channel turns that name into your sender key.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The resolution procedure, one channel at a time:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 1. Prefer your own record.<\/strong> If the row can be joined to your outbound table (by <code>uuId<\/code> or the composite key your ingestion uses), take <code>sender_key<\/code> from the outbound row and stop. This covers most outbound traffic.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 2. Extract the channel&#8217;s sender field.<\/strong><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Row<\/th><th>Field to read<\/th><th>Normalise to<\/th><\/tr><\/thead><tbody><tr><td>SMS delivery report<\/td><td><code>senderName<\/code><\/td><td>Uppercase, trimmed<\/td><\/tr><tr><td>WhatsApp report<\/td><td><code>wabaNumber<\/code><\/td><td>Digit string<\/td><\/tr><tr><td>WhatsApp inbox<\/td><td><code>wabaNumber<\/code><\/td><td>Digit string<\/td><\/tr><tr><td>WhatsApp analytics<\/td><td><code>waNumber<\/code><\/td><td>Digit string<\/td><\/tr><tr><td>RCS delivery report<\/td><td><code>botName<\/code><\/td><td>Exact string<\/td><\/tr><tr><td>RCS inbox<\/td><td><code>botId<\/code><\/td><td>Integer as string<\/td><\/tr><tr><td>Telegram dlr or inbox<\/td><td>None<\/td><td>The account&#8217;s configured bot<\/td><\/tr><tr><td>SMS inbound push<\/td><td><code>phonecode<\/code>, <code>keyword<\/code><\/td><td>Digits; uppercase keyword<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 3. Look up the registry by identity and time.<\/strong> Match the normalised value against the channel&#8217;s identity column, restricted to rows where the event timestamp falls between <code>valid_from<\/code> and <code>valid_to<\/code>. Time matters for RCS names and for any sender that was retired and re-created.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 4. Handle a miss explicitly.<\/strong> Write the row with <code>sender_key<\/code> null and a <code>sender_unresolved<\/code> flag, and alert. Never guess from brand keywords in the message text.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Step 5. Backfill.<\/strong> When the sync job adds a sender (for example a WhatsApp number discovered through analytics), re-run resolution over unresolved rows in the retention window.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"code-samples\" class=\"wp-block-heading\">Four Code Samples, Four Traps<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Every sample authenticates with the <code>apikey<\/code> header plus <code>userid<\/code>, which keeps passwords out of request bodies and access logs.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Python: SMS sender create returns no <code>sId<\/code>, and <code>count<\/code> is an object<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>import os\nimport requests\n\nBASE = \"https:\/\/unify.smsgateway.center\/SMSApi\/senderid\"\nUSERID = os.environ&#91;\"SGC_USERID\"]\nHEADERS = {\"apikey\": os.environ&#91;\"SGC_APIKEY\"]}\n\ndef _call(action: str, **fields) -&gt; dict:\n    r = requests.post(f\"{BASE}\/{action}\",\n                      data={\"userid\": USERID, \"output\": \"json\", **fields},\n                      headers=HEADERS, timeout=30)\n    r.raise_for_status()\n    resp = r.json().get(\"response\", {})\n    if resp.get(\"status\") != \"success\":\n        raise RuntimeError(f\"senderid\/{action} failed: {resp}\")\n    return resp\n\ndef list_senders() -&gt; tuple&#91;dict, list&#91;dict]]:\n    resp = _call(\"read\")\n    buckets = resp.get(\"count\") or {}          # an object: total, pending, active, rejected\n    rows = &#91;item&#91;\"senderid\"] for item in resp.get(\"senderidList\", &#91;])]\n    return buckets, rows\n\ndef create_sender(name: str) -&gt; dict:\n    name = name.strip().upper()\n    _call(\"create\", senderid=name)\n    _, rows = list_senders()\n    matches = &#91;r for r in rows if r.get(\"senderName\", \"\").upper() == name]\n    if len(matches) != 1:\n        raise LookupError(f\"expected one row for {name}, found {len(matches)}\")\n    row = matches&#91;0]\n    return {\"sms_s_id\": row&#91;\"sId\"], \"sms_sender_id\": name,\n            \"raw_status\": row.get(\"isEnabled\"), \"added_ms\": row.get(\"addTime\")}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The traps: the identifier needed for <code>update<\/code> and <code>delete<\/code> only exists on the list, and <code>count<\/code> is a four-key object that a generic paging helper would misread.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Node.js: the WhatsApp number arrives as three types<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Normalise any wabaNumber \/ waNumber value to a digit string.\nfunction normWaba(value) {\n  if (value === null || value === undefined) return null;\n  const s = String(value).replace(\/\\D\/g, \"\");\n  return s.length ? s : null;\n}\n\n\/\/ Report rows: quote wabaNumber, mobileNo and uuId before JSON.parse, so\n\/\/ no numeric conversion ever happens.\nfunction parseReportBody(text) {\n  const safe = text.replace(\n    \/\"(wabaNumber|mobileNo|uuId)\"\\s*:\\s*(\\d+)\/g,\n    (_m, key, digits) =&gt; `\"${key}\":\"${digits}\"`\n  );\n  return JSON.parse(safe);\n}\n\nfunction senderFromWhatsAppRow(row) {\n  return normWaba(row.wabaNumber ?? row.waNumber);\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The trap: strict equality between a number and a string is always false, so a registry lookup keyed on strings silently misses every report row unless the value is normalised first. Quoting the fields before parsing also protects the nineteen-digit <code>uuId<\/code> on the same row.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Java: RCS delivery rows name the bot, the bot list gives the id<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>import com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.net.URI;\nimport java.net.URLEncoder;\nimport java.net.http.*;\nimport java.nio.charset.StandardCharsets;\nimport java.util.*;\n\npublic final class RcsBots {\n    private static final ObjectMapper M = new ObjectMapper();\n    private static final HttpClient HTTP = HttpClient.newHttpClient();\n\n    \/** Returns botName -&gt; botId for currently active bots. Merge into a persisted snapshot. *\/\n    public static Map&lt;String, String&gt; activeBots(String userid, String apikey) throws Exception {\n        String qs = \"userid=\" + URLEncoder.encode(userid, StandardCharsets.UTF_8)\n                  + \"&amp;output=json&amp;action=list\";\n        HttpRequest req = HttpRequest.newBuilder(\n                URI.create(\"https:\/\/unify.smsgateway.center\/rest\/rcs\/v1\/bots?\" + qs))\n            .header(\"apikey\", apikey).GET().build();\n        JsonNode root = M.readTree(HTTP.send(req, HttpResponse.BodyHandlers.ofString()).body());\n        if (!\"success\".equals(root.path(\"status\").asText())) {\n            throw new IllegalStateException(\"bot list failed: \" + root.path(\"reason\").asText());\n        }\n        Map&lt;String, String&gt; byName = new HashMap&lt;&gt;();\n        for (JsonNode b : root.path(\"botsList\")) {       \/\/ bind only after success\n            if (b.path(\"isSandbox\").asInt() == 1) continue;\n            byName.put(b.path(\"name\").asText(), String.valueOf(b.path(\"botId\").asLong()));\n        }\n        return byName;\n    }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The trap: a live lookup misses every bot that is no longer active, so this method feeds a snapshot table rather than answering resolution queries directly. The error payload flips <code>botsList<\/code> to an empty array, which is why the status is read before the list is bound.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">PHP: routing inbound SMS on long code plus keyword<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;?php\n\/\/ Registry loaded from your database: &#91;longCode]&#91;KEYWORD] =&gt; sender_key\nfunction route_inbound(array $q, array $registry, ?string $defaultLongCode): array {\n    $phone   = preg_replace('\/\\D\/', '', $q&#91;'phoneno'] ?? '');\n    $content = (string)($q&#91;'content'] ?? '');\n    $keyword = strtoupper(trim((string)($q&#91;'keyword'] ?? '')));\n    $code    = preg_replace('\/\\D\/', '', (string)($q&#91;'phonecode'] ?? ''));\n\n    if ($code === '' &amp;&amp; $defaultLongCode !== null) {\n        $code = $defaultLongCode;               \/\/ only safe with exactly one long code\n    }\n    if ($code !== '' &amp;&amp; isset($registry&#91;$code]&#91;$keyword])) {\n        return &#91;'sender_key' =&gt; $registry&#91;$code]&#91;$keyword], 'phone' =&gt; $phone, 'content' =&gt; $content];\n    }\n    foreach ($registry as $lc =&gt; $byKeyword) {  \/\/ keyword-only fallback\n        if ($keyword !== '' &amp;&amp; isset($byKeyword&#91;$keyword])) {\n            return &#91;'sender_key' =&gt; $byKeyword&#91;$keyword], 'phone' =&gt; $phone, 'content' =&gt; $content];\n        }\n    }\n    return &#91;'sender_key' =&gt; null, 'phone' =&gt; $phone, 'content' =&gt; $content, 'review' =&gt; true];\n}\n\n$result = route_inbound($_GET, $registry, count($registry) === 1 ? array_key_first($registry) : null);\nhttp_response_code(200);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The trap: <code>phonecode<\/code> and <code>keyword<\/code> are optional in the push, so a router that assumes both are present drops real replies. Unmatched messages go to a review queue, and the handler still returns 200 so the push is acknowledged.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"sender-lifecycle\" class=\"wp-block-heading\">Renaming, Retiring and Replacing a Sender<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>SMS sender ID.<\/strong> Change the header on the DLT portal first. Then call <code>SMSApi\/senderid\/update<\/code> with the <code>sId<\/code> as <code>id<\/code> and the new <code>senderid<\/code>, re-read the list, and check the new name and its <code>isEnabled<\/code> state. Templates are registered against headers on DLT, so update the pairing on DLT and re-sync the <code>sender_template<\/code> table before sending with the new name. To retire, close the registry row with <code>valid_to<\/code>, stop routing to it, and only then call <code>delete<\/code>. The knowledge base entry on <a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/kb\/what-happens-if-i-delete-a-sender-id-by-mistake\/\">deleting a sender ID by mistake<\/a> notes that a deleted sender cannot be used until it is added again and that past messages are unaffected.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>WhatsApp number.<\/strong> A number change is an onboarding event, not an API call. Templates are scoped per number, so a new number starts with its own template set; plan template approval for the new number before cutting traffic over, and run both numbers in parallel until the old one&#8217;s report window has closed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>RCS bot.<\/strong> A new bot gets a new <code>botId<\/code> and appears in <code>botsList<\/code> once active. Templates are listed per bot, so the same template-approval lead time applies. Keep the old bot&#8217;s row open in the registry until its delivery rows stop arriving.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Telegram bot.<\/strong> A <code>save<\/code> with a new token swaps the account&#8217;s bot immediately. Record the old and new handles with validity dates so history stays attributable, and tell subscribers: Telegram chats belong to a bot, so users who started the old bot have not started the new one.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"ten-mistakes\" class=\"wp-block-heading\">Ten Mistakes That Break Sender Attribution<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Comparing a WhatsApp number as a number on one side and a string on the other.<\/strong> Normalise to a digit string at the parser boundary.<\/li>\n\n\n\n<li><strong>Treating <code>isEnabled<\/code> as a boolean.<\/strong> It holds a status string such as <code>\"Pending\"<\/code>. A truthy check marks a pending sender as enabled.<\/li>\n\n\n\n<li><strong>Reading <code>count<\/code> on the sender list as an integer.<\/strong> It is an object with <code>total<\/code>, <code>pending<\/code>, <code>active<\/code> and <code>rejected<\/code>.<\/li>\n\n\n\n<li><strong>Resolving RCS delivery rows through a live bot lookup.<\/strong> Inactive bots drop out of <code>botsList<\/code>. Resolve from a snapshot.<\/li>\n\n\n\n<li><strong>Sending with a sender ID that is not paired with the template.<\/strong> The message fails with <code>SENDERID_MISMATCH<\/code>. Check the pair before the send.<\/li>\n\n\n\n<li><strong>Deleting a registry row when a sender is retired.<\/strong> Late delivery rows then cannot be attributed. Close it with a validity date instead.<\/li>\n\n\n\n<li><strong>Letting sandbox bots into production routing.<\/strong> Filter on <code>isSandbox<\/code>, not on a naming convention.<\/li>\n\n\n\n<li><strong>Looping WhatsApp report queries over a hard-coded number list.<\/strong> A number missing from the list means its traffic never reaches your reports. Cross-check with analytics grouped by <code>waNumber<\/code>.<\/li>\n\n\n\n<li><strong>Assuming <code>phonecode<\/code> and <code>keyword<\/code> are always present on inbound SMS.<\/strong> Both are optional in the push. Route with fallbacks and a review queue.<\/li>\n\n\n\n<li><strong>Rotating the Telegram token without recording it.<\/strong> The token is write-only and the rows carry no bot field, so the registry is the only record of which bot sent what, when.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"decision-matrix\" class=\"wp-block-heading\">Decision Matrix<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>If you need to&#8230;<\/th><th>SMS<\/th><th>WhatsApp<\/th><th>RCS<\/th><th>Telegram<\/th><\/tr><\/thead><tbody><tr><td>Add a new sender from your own code<\/td><td><code>SMSApi\/senderid\/create<\/code>, after DLT registration<\/td><td>Not available; onboarding<\/td><td>Not available; provisioning<\/td><td><code>rest\/tg\/v1\/setup<\/code> <code>save<\/code><\/td><\/tr><tr><td>Know a sender is usable<\/td><td><code>isEnabled<\/code> indicates active<\/td><td>Number is onboarded and templates approved<\/td><td>Present in <code>botsList<\/code> with the right <code>isSandbox<\/code><\/td><td>Setup <code>get<\/code> returns your bot<\/td><\/tr><tr><td>Run two brands side by side<\/td><td>Two headers<\/td><td>Two numbers<\/td><td>Two bots<\/td><td>Two accounts<\/td><\/tr><tr><td>Attribute a delivery row<\/td><td><code>senderName<\/code><\/td><td><code>wabaNumber<\/code><\/td><td><code>botName<\/code> via snapshot<\/td><td>The account&#8217;s bot<\/td><\/tr><tr><td>Attribute an inbox row<\/td><td><code>phonecode<\/code> plus <code>keyword<\/code><\/td><td><code>wabaNumber<\/code><\/td><td><code>botId<\/code><\/td><td>The account&#8217;s bot<\/td><\/tr><tr><td>List all senders<\/td><td><code>SMSApi\/senderid\/read<\/code><\/td><td>Analytics <code>groupBy=waNumber<\/code> plus your configuration<\/td><td><code>rest\/rcs\/v1\/bots<\/code> (active only)<\/td><td>Setup <code>get<\/code><\/td><\/tr><tr><td>Test safely<\/td><td>Sandbox account<\/td><td>Test numbers you control<\/td><td>A bot with <code>isSandbox<\/code> of <code>1<\/code><\/td><td>A test account&#8217;s bot<\/td><\/tr><tr><td>Change a sender&#8217;s name<\/td><td>DLT first, then <code>update<\/code><\/td><td>New number<\/td><td>New bot<\/td><td><code>save<\/code> with new details<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">For how senders feed into the wider multi-channel picture, including which channel to fall back to when a sender cannot reach a recipient, see <a href=\"https:\/\/www.smsgatewaycenter.com\/multi-channel-fallback\/\">multi-channel fallback<\/a>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"checklist\" class=\"wp-block-heading\">Pre-Launch Checklist<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Registry<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Every sender has a surrogate <code>sender_key<\/code> and a <code>brand<\/code>.<\/li>\n\n\n\n<li>Every channel identifier is stored as text, normalised (uppercase header, digit-only number).<\/li>\n\n\n\n<li>Senders are retired with <code>valid_to<\/code>, never deleted.<\/li>\n\n\n\n<li>Outbound rows record <code>sender_key<\/code> at submit time.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>SMS<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Sender IDs are validated as one to six uppercase letters or digits before create.<\/li>\n\n\n\n<li><code>sId<\/code> is captured by reading the list after every create.<\/li>\n\n\n\n<li><code>isEnabled<\/code> is compared against an allowlist of active values, never tested for truthiness.<\/li>\n\n\n\n<li>Every send checks that <code>senderid<\/code> is in the template&#8217;s <code>senderIds<\/code>.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>WhatsApp<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>wabaNumber<\/code> and <code>waNumber<\/code> are normalised to digit strings before comparison.<\/li>\n\n\n\n<li>Report bodies are parsed with numeric identifiers quoted first.<\/li>\n\n\n\n<li>Report jobs loop over every number, and the list is cross-checked against analytics grouped by <code>waNumber<\/code>.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>RCS<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The bot list is snapshotted daily, including <code>name<\/code>, <code>botId<\/code> and <code>isSandbox<\/code>.<\/li>\n\n\n\n<li>Delivery rows resolve <code>botName<\/code> through the snapshot with a time window.<\/li>\n\n\n\n<li>Sandbox bots are excluded from production routing.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Telegram<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The bot token is written to your secrets manager when saved.<\/li>\n\n\n\n<li>Bot handle changes are recorded with dates.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Inbound SMS<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Keywords are uppercased and trimmed before routing.<\/li>\n\n\n\n<li>Missing <code>phonecode<\/code> or <code>keyword<\/code> falls back gracefully to a review queue.<\/li>\n\n\n\n<li>The handler returns 200 after enqueueing, whatever the routing result.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Operations<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Unresolved report rows raise an alert and are backfilled when the registry changes.<\/li>\n\n\n\n<li>Every sender list response is stored raw for a rolling window.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"unspecified-behaviour\" class=\"wp-block-heading\">Unspecified Behaviour and How to Code Around It<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The behaviours below are not pinned down by anything you can read ahead of time. Each item gives the choice that stays safe whichever way the behaviour turns out, and none of them require you to wait for an answer.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>One. Treat only observed &#8220;active&#8221; values of <code>isEnabled<\/code> as usable.<\/strong> The sample shows <code>\"Pending\"<\/code>, and the count buckets suggest active and rejected states. Keep an allowlist populated from senders that have actually delivered, and treat every other value as not usable. An unknown value then fails closed: a delay, not a rejected campaign.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Two. Pass the <code>sId<\/code> as the <code>id<\/code> for update and delete, then re-read.<\/strong> The update and delete calls take an <code>id<\/code>, and <code>sId<\/code> is the only identifier the list returns. Confirm the change by re-reading and checking the row with that <code>sId<\/code>. The confirmation read keeps the operation correct however the identifier is resolved internally.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Three. Enforce the six-character rule even if the platform accepts more.<\/strong> The create page describes Indian sender IDs as up to six alphanumeric characters. A six-character validator never produces a header that some stricter check will reject, and it costs nothing if the limit is looser elsewhere.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Four. Split <code>senderIds<\/code> on commas even when you have only seen one value.<\/strong> The field name is plural and a template can map to several headers. A split that finds one value returns a one-element set; a split that finds several returns all of them. Either way the pair check is correct.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Five. Snapshot the RCS bot list before you need history.<\/strong> Deactivated bots drop out of the live list, and delivery rows name bots rather than numbering them. A daily snapshot of <code>botId<\/code>, <code>name<\/code> and <code>isSandbox<\/code> turns &#8220;which bot was this?&#8221; into a join.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Six. Resolve RCS names within a time window.<\/strong> Whether a bot name can be reused after a bot is replaced is not something to rely on either way. Match <code>botName<\/code> against snapshot rows whose validity covers the delivery timestamp, and ambiguity cannot arise.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Seven. Quote WhatsApp numeric identifiers before parsing, even if they fit today.<\/strong> A twelve-digit number is safe in a double, a nineteen-digit <code>uuId<\/code> is not, and a future field on the same row might be either. Quoting every digit run for known identifier keys before <code>JSON.parse<\/code> is correct whatever the lengths turn out to be.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Eight. Build the WhatsApp number list from two sources.<\/strong> Keep numbers in configuration and also derive them from analytics grouped by <code>waNumber<\/code>. Any number that appears in one source but not the other raises an alert. Two independent sources catch an omission that either alone would hide.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Nine. Route inbound SMS without assuming <code>phonecode<\/code> is present.<\/strong> With one long code, default to it; with several, route on keyword and queue the rest for review. The router is correct whether the platform always sends <code>phonecode<\/code> or only sometimes does.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Ten. Treat a Telegram <code>save<\/code> as irreversible for reads.<\/strong> The token cannot be read back, so capture it in your secrets manager in the same transaction that calls <code>save<\/code>, and record the handle and date in the registry. You then never need the platform to tell you which token is live.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Eleven. Close registry rows instead of deleting platform senders first.<\/strong> Delivery rows can arrive after a sender is removed. Set <code>valid_to<\/code> in your registry, wait for the report window to pass, then call the platform delete. Attribution survives however long late rows keep arriving.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Twelve. Store raw sender list responses for a rolling window.<\/strong> When a send fails with a sender-related cause, the stored list shows exactly what state the sender was in at the time. A cheap daily blob replaces a guess with a lookup.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 id=\"faqs\" class=\"wp-block-heading\">FAQs<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>What is a sender ID on SMSGatewayCenter?<\/strong><br>It is the header shown as the sender of an SMS, such as <code>SMSGAT<\/code>. In India it must be registered on an operator DLT portal first; the platform record is then managed through <code>SMSApi\/senderid\/create<\/code>, <code>read<\/code>, <code>update<\/code> and <code>delete<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>How do I get the ID of a sender I just created?<\/strong><br>The create response returns no identifier. Call <code>SMSApi\/senderid\/read<\/code>, find the row whose <code>senderName<\/code> matches, and store its <code>sId<\/code> as text.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Is <code>isEnabled<\/code> true or false?<\/strong><br>Neither. It is a status string; the published sample value is <code>\"Pending\"<\/code>. Compare it against known values rather than testing it as a boolean.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Why does my SMS fail with SENDERID_MISMATCH?<\/strong><br>The sender ID you used is not the one registered with the template. Check the template&#8217;s <code>senderIds<\/code> and send with a header that appears there.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Can one template be used with several sender IDs?<\/strong><br>Yes, if each sender ID is mapped to the template on DLT and in your account.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>What identifies the sender on WhatsApp?<\/strong><br>The WhatsApp Business number, passed as <code>wabaNumber<\/code>. It is required on send, template and media writes, and report queries.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Why does <code>wabaNumber<\/code> come back as a number on reports and a string on the inbox?<\/strong><br>The two endpoints serialise it differently, and analytics renames it <code>waNumber<\/code>. Normalise every occurrence to a digit string before comparing.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>How do I list my RCS bots?<\/strong><br>Call <code>GET rest\/rcs\/v1\/bots<\/code> with <code>output=json<\/code>. Only active bots are returned, and <code>isSandbox<\/code> of <code>1<\/code> marks a sandbox bot.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>How do I know which RCS bot a delivery row came from?<\/strong><br>The delivery row carries <code>botName<\/code>. Map it to a <code>botId<\/code> through a stored snapshot of the bot list, because inactive bots are not returned by the live list.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Can I run several Telegram bots on one account?<\/strong><br>No. The account holds one bot, configured through <code>rest\/tg\/v1\/setup<\/code>. Separate Telegram identities need separate accounts.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Can I read my Telegram bot token back?<\/strong><br>No. It is write-only. Store it in your own secrets manager when you save it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>How do I tell which campaign an inbound SMS belongs to?<\/strong><br>Route on the long code (<code>phonecode<\/code>) and <code>keyword<\/code> from the push. Both are optional, so fall back to keyword-only routing and a review queue.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Should I delete a sender ID I no longer use?<\/strong><br>Close it in your own registry first, wait until its delivery reports have stopped arriving, then delete it on the platform. A deleted sender cannot be used until it is added again.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Where should the sender be recorded for attribution?<\/strong><br>On your outbound message row at submit time. Report fields are the fallback, not the primary source.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you are planning a multi-brand or multi-channel rollout and want a second pair of eyes on the sender design, <a href=\"https:\/\/www.smsgatewaycenter.com\/contact\/\">talk to the SMSGatewayCenter team<\/a>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Every message traced to the brand that sent it<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Build your sender registry against the SMSGatewayCenter developer API and test create, sync and resolution in the sandbox before live traffic.<\/p>\n\n\n\n<div class=\"wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex\">\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link has-white-color has-vivid-cyan-blue-background-color has-text-color has-background has-link-color wp-element-button\" href=\"https:\/\/www.smsgatewaycenter.com\/developer-api\/\">Explore the Developer API<\/a><\/div>\n\n\n\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link has-vivid-purple-background-color has-background wp-element-button\" href=\"https:\/\/www.smsgatewaycenter.com\/demo\/\">Try the Demo<\/a><\/div>\n<\/div>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Recent Articles<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/message-template-management-four-channels\/\">Message Template Management Across SMS, RCS, WhatsApp and Telegram: One API Comparison<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/reconciling-messaging-invoice-four-channels\/\">Reconciling a Messaging Invoice Across Four Channels: Credits, Currency and the Rate Plan API<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/receiving-messages-four-channel-inbox-contracts\/\">Receiving Messages on Four Channels: Inbox API Contracts Compared<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/whatsapp-business-api-wire-contract\/\">WhatsApp Business API Wire Contract: Ten Endpoints, Two Base Paths, Five Envelopes<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/www.smsgatewaycenter.com\/blog\/telegram-messaging-api-chat-id-model\/\">Telegram Messaging API: Eight Endpoints, the Chat ID Model, and the Error Shape That Breaks Typed Clients<\/a><\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n","protected":false},"excerpt":{"rendered":"<p>A message on SMSGatewayCenter can come from five different kinds of sender: a DLT sender ID, a WhatsApp Business number, an RCS bot, the account&#8217;s single Telegram bot, or a long code plus keyword on the way in. Each has its own API, its own spelling in responses, its own JSON type and its own approval model. This is the field-level map and the registry design that ties them together.<\/p>\n","protected":false},"author":118,"featured_media":3047,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2010],"tags":[441,2284,1986,2237,2282,481,2244,2285,2283],"class_list":["post-3042","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-developer-guides","tag-dlt-header","tag-long-code","tag-multi-channel-messaging","tag-rcs-bot","tag-sender-id-api","tag-sms-api","tag-telegram-bot","tag-two-way-sms-keyword","tag-waba-number"],"_links":{"self":[{"href":"https:\/\/www.smsgatewaycenter.com\/blog\/wp-json\/wp\/v2\/posts\/3042","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.smsgatewaycenter.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.smsgatewaycenter.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.smsgatewaycenter.com\/blog\/wp-json\/wp\/v2\/users\/118"}],"replies":[{"embeddable":true,"href":"https:\/\/www.smsgatewaycenter.com\/blog\/wp-json\/wp\/v2\/comments?post=3042"}],"version-history":[{"count":0,"href":"https:\/\/www.smsgatewaycenter.com\/blog\/wp-json\/wp\/v2\/posts\/3042\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.smsgatewaycenter.com\/blog\/wp-json\/wp\/v2\/media\/3047"}],"wp:attachment":[{"href":"https:\/\/www.smsgatewaycenter.com\/blog\/wp-json\/wp\/v2\/media?parent=3042"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.smsgatewaycenter.com\/blog\/wp-json\/wp\/v2\/categories?post=3042"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.smsgatewaycenter.com\/blog\/wp-json\/wp\/v2\/tags?post=3042"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}