SMS Channel
Send and receive SMS through the same Webhook & Send API used for the other channels — same authentication, idempotency, signing secret, and response shape. This page covers the SMS-specific details; the shared envelope, signing, and core actions live on the Webhook & Send API page.
Add-on · $29/month · Requires Professional+ planOverview
SMS uses the same authentication, idempotency, signing secret, and response shape as every other channel — just a different event type and an extra routing field (to_phone).
Requirements: SMS access on your tier (Professional or Business) and a provisioned Twilio number under Settings → SMS in the portal. Without a provisioned number, the sms.message_received checkbox on the Webhook & Send API page is disabled.
Inbound vs. outbound
| Direction | How it works | Counts toward SMS limit? |
|---|---|---|
| Inbound (customer texts your number) | Twilio → ChatGenius → HMAC-signed POST to your endpoint with event_type: "sms.message_received" | No — inbound is free |
| Outbound (you send via Send-Reply API) | Your server → ChatGenius → Twilio → recipient. Triggered by action: "send_message" with to_phone | Yes — each successful outbound API call counts as 1 toward your monthly SMS quota, regardless of how many segments Twilio splits the message into |
Supported actions
SMS supports only the send_message action. The react, unreact, and typing_on actions are Meta-only and are rejected when used with an SMS conversation.
Pure-relay mode
To handle all SMS conversations yourself with no AI from ChatGenius, either disable the SMS AI Assistant under Settings → SMS → AI Assistant (SMS-only), or enable Global External Reply Mode to pause AI across every channel at once (see Global External Reply Mode). With relay on:
- Inbound SMS still hits your endpoint with the full payload — signed, validated, ready to process
- ChatGenius does not generate any AI reply
- You can still send replies via the Send-Reply API (using to_phone or conversation_id)
- The conversation and message history are still recorded in your portal so your team has visibility
Inbound Payload
When a customer texts your provisioned ChatGenius number, ChatGenius forwards an sms.message_received event with the standard envelope plus an SMS-specific message.sms sub-block. source.channel is always twilio_sms.
{
"event_id": "7f7a80c84bf17c4b5b72974c97365a77",
"event_type": "sms.message_received",
"occurred_at": "2026-04-27T23:22:10Z",
"source": {
"platform": "sms",
"channel": "twilio_sms"
},
"conversation": {
"id": 119301
},
"customer": {
"platform_user_id": "+15551234567",
"phone": "+15551234567"
},
"message": {
"id": "SMdb4342728f73bca6a83770a705e27b31",
"text_raw": "Hi, what time do you open Saturday?",
"attachments": [],
"sms": {
"to_phone": "+15559876543",
"from_phone": "+15551234567",
"num_segments": 1,
"num_media": 0,
"encoding": "GSM-7"
}
},
"meta": {
"version": "v1",
"payload_profile": "full",
"generated_at": "2026-04-27T23:22:10Z"
}
}
SMS-specific fields
| Field | Type | Description |
|---|---|---|
| customer.platform_user_id | string | The customer's phone number in E.164 format. Always present. |
| customer.phone | string | Same as platform_user_id — a convenience for SMS payloads. Present in full payload profile only. |
| message.id | string | The Twilio MessageSid. Stable identifier; use for deduplication. |
| message.text_raw | string | The full SMS body. Multi-segment SMS arrives concatenated — you receive the joined text, not individual segments. |
| message.sms.to_phone | string | Your ChatGenius (Twilio) number that received the SMS, in E.164 format |
| message.sms.from_phone | string | The customer's phone number in E.164 format (same as customer.platform_user_id) |
| message.sms.num_segments | integer | How many SMS segments the inbound message used (informational) |
| message.sms.num_media | integer | The number of media items Twilio reported. The count is forwarded for visibility, but MMS media URLs are not included in v1 — message.attachments is always an empty array for SMS in v1. |
| message.sms.encoding | string | GSM-7 or UCS-2 — detected from the message body content |
Signature verification, retry behavior, and timestamp validation are identical to every other channel — same X-SumGenius-Signature header, same per-account signing secret, same algorithm. See Verifying Signatures.
Sending SMS
Send an SMS by calling the same Send-Reply endpoint used for DMs, with to_phone instead of conversation_id. The platform is auto-set to sms.
Request body
| Field | Required | Description |
|---|---|---|
| action | optional | Defaults to send_message. SMS supports only this action. |
| idempotency_key | required | Your unique key for this request (max 128 chars). Same key returns "status": "duplicate" — no second SMS sent. |
| to_phone | required* | Recipient phone number in E.164 format (e.g. +15551234567). *Required if conversation_id is not provided. Numbers are auto-normalized — spaces, dashes, parens, and a leading 1 without + are accepted. |
| conversation_id | required* | The conversation.id from a received SMS event. *Use this OR to_phone. |
| message_text | required | The SMS body. Max 1,600 chars (GSM-7) or 1,530 chars (UCS-2). See Encoding & Segments. |
The sender number is automatically set to your provisioned sms_phone_number. You cannot specify from_phone — sender selection is server-controlled to enforce A2P 10DLC compliance.
curl -X POST https://sumgenius.ai/api/meta/webhook-send.php \
-H "X-SumGenius-Api-Key: sgwh_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"idempotency_key": "refill-reminder-001",
"action": "send_message",
"to_phone": "+15551234567",
"message_text": "Your prescription is ready for pickup. Reply STOP to opt out."
}'
SMS-specific success response fields
SMS responses follow the same Response Format as DM responses, with these additions:
| Field | Description |
|---|---|
| platform | Always "sms" |
| to_phone | Echoes the recipient phone number (only present when to_phone was sent in the request) |
Encoding & Segments
SMS uses two encodings depending on the characters in your message body. ChatGenius detects the encoding automatically and validates against the corresponding cap.
| Encoding | Triggered by | Single segment | Concatenated segment | Hard cap |
|---|---|---|---|---|
| GSM-7 | Latin alphabet, common punctuation, basic European accents | 160 chars | 153 chars | 1,600 chars |
| UCS-2 | Any emoji, non-Latin script (Arabic, Chinese, etc.), or smart quotes | 70 chars | 67 chars | 1,530 chars |
One emoji can drop your character limit by more than half. A 160-character GSM-7 message that fit in 1 segment becomes a 70-char-per-segment UCS-2 message the moment a single emoji is added — same text, now 3 segments. Segments matter for carrier cost and throughput, but ChatGenius counts each successful outbound Send-Reply API call as 1 SMS quota unit.
Quota counting rules
- Inbound SMS — free, never counted (regardless of segments)
- Outbound SMS — each successful Send-Reply API call counts as 1 toward your monthly quota, regardless of how many segments Twilio splits the message into
- A 200-char GSM-7 message = 1 quota unit (Twilio sends 2 segments; we count 1)
- A 200-char UCS-2 message = 1 quota unit (Twilio sends 3 segments; we count 1)
Segment count still matters for Twilio's own carrier-level throughput limits — sending many long messages quickly can hit Twilio's per-second segment cap before it hits our monthly quota. Keep messages concise where possible.
Opt-out handling (STOP / START)
SMS opt-out is handled automatically at multiple layers — you don't implement any of this:
- Twilio carrier-level Advanced Opt-Out: when a recipient texts STOP, Twilio blocks all future outbound SMS to that number from your Messaging Service, regardless of any application-layer check.
- ChatGenius app-level fast-fail: we mirror the opt-out so future API calls return 410 with "Recipient has opted out (replied STOP)." immediately, without consuming a Twilio API call.
- Re-subscription: if the recipient texts START, UNSTOP, or YES, both layers re-enable sending automatically.
You should still include opt-out language in your initial messages (e.g. "Reply STOP to opt out.") to comply with carrier and CASL/TCPA requirements.
SMS Error Reference
SMS errors follow the same general error response shape. The codes specific to SMS are:
| HTTP | Trigger | Error message |
|---|---|---|
| 400 | to_phone doesn't match E.164 | to_phone must be in E.164 format (e.g. +15551234567). |
| 403 | SMS not enabled on your account tier | SMS access is not enabled for this account. |
| 410 | Recipient has opted out (replied STOP) | Recipient has opted out (replied STOP). |
| 412 | No Twilio number provisioned for the account | SMS not provisioned for this account. Provision a Twilio number first. |
| 422 | Message exceeds GSM-7 (1,600) or UCS-2 (1,530) cap, or an unsupported action was used | message_text exceeds 1600 characters for SMS GSM-7 (1700 provided). |
| 429 | Monthly SMS quota exceeded | Monthly SMS limit reached for this account. |
| 502 | Twilio returned an error (network, invalid number, landline, region not enabled, etc.) | SMS delivery failed: <Twilio error message>. |
| 503 | SMS add-on is temporarily disabled platform-wide | SMS webhook add-on is temporarily disabled. |
Permanent failures (invalid recipient, opted out, over-length) are not retried — they go straight to dead_letter. Transient failures (Twilio 5xx, 429, network errors) are retried with the same backoff schedule used for Meta deliveries.