Skip to main content

AccountSMS

Fires when a text message is sent or received on any of the account's numbers — including messages sent from the portal's message center and from POST /sms/messages.

Payload

{
"id": 88214417,
"direction": "received",
"startTime": "2026-08-11T13:20:01Z",
"fromNumber": "13475550123",
"toNumber": "12125550188",
"message": "Can we move Tuesday to 9am?",
"media": "",
"webhookType": "AccountSMS"
}
FieldTypeDescription
idintegerThe message id. Use it to deduplicate — there is no delivery id.
directionstringreceived (inbound to your number) or sent (outbound from it).
startTimestringRFC 3339 UTC. Named startTime, not time.
fromNumberstringSender, normalized with a country code.
toNumberstringRecipient, normalized with a country code.
messagestringThe body. Attacker-controlled — escape before rendering.
mediastringMMS attachments, as a JSON-encoded array of URLs inside a string — see below. Empty for a plain SMS.
webhookTypestringAlways AccountSMS.

:::caution fromNumber and toNumber swap with direction On a received message, toNumber is your line. On a sent message, fromNumber is. Pick the field by direction — do not assume either position is yours. :::

Reacting to it

The two numbers are exactly what the conversation endpoints take — a conversation has no id, it is the pair — so you can act on the thread with no lookup first:

# your line = toNumber, other party = fromNumber, on a received message
curl -X POST ".../sms/conversations/12125550188/13475550123/resolve" \
-H "Authorization: Bearer $TB_KEY"

MMS media

media is a string containing a JSON array of URLs — not an array, not a single URL:

"media": "[\"https://mms.example.net/9f2c/photo.jpg\"]"

Parse it, with a fallback — an older value can be a bare URL with no JSON around it:

function mediaUrls(media) {
if (!media) return [];
try {
const parsed = JSON.parse(media);
return Array.isArray(parsed) ? parsed : [String(parsed)];
} catch {
return [media]; // not JSON: treat the whole value as one URL
}
}

The URLs need no Authorization header, so anyone holding one can fetch the attachment. They are also not permanent — download the file when the event arrives rather than storing the link.

Delivery status

This fires when a message is recorded — for an outbound message, that the gateway accepted it. There is no separate delivered or failed event.

Gotchas

  • Your own API sends fire this event. An auto-responder that replies to every AccountSMS will reply to itself. Filter on direction == "received".
  • Nothing fires on conversation state. Resolving a thread in the portal emits no event; this is about messages only.
  • On an outbound group message, toNumber is "0". A group thread is one row whose recipient column is 0, and the participant list is not in this payload.