Webhook events reference
Every textbee webhook event with a full JSON payload and field table: received, sent, delivered, failed and unknown state, plus ordering rules.
Updated
textbee sends six event types to webhook subscriptions. Five of them fire today: MESSAGE_RECEIVED, MESSAGE_SENT, MESSAGE_DELIVERED, MESSAGE_FAILED and UNKNOWN_STATE. Each payload is a flat JSON object with the same envelope fields plus fields for that event. To create a subscription and verify signatures, see Webhooks.
Envelope fields
Every event has these six fields.
| Field | Type | Description |
|---|---|---|
smsId | string | ID of the stored message. The same ID appears in the messages endpoint as _id. |
message | string | Message text. |
deviceId | string | ID of the device that received or sent the message. |
webhookSubscriptionId | string | ID of the subscription that produced this delivery. |
webhookEvent | string | Event name, for example MESSAGE_RECEIVED. |
idempotencyKey | string | Unique per delivery. Every retry of one delivery carries the same key. |
Switch on webhookEvent to handle each event type.
MESSAGE_RECEIVED
Fires when the phone uploads an incoming SMS to textbee. Receive SMS must be on in the app. See Receiving SMS.
{
"smsId": "66f7c1e2a4d5e6f7a8b9c0d1",
"message": "YES, please confirm my booking",
"deviceId": "664a9b8cd0e1f2a3b4c5d6e7",
"webhookSubscriptionId": "664a9b8cd0e1f2a3b4c5d6e8",
"webhookEvent": "MESSAGE_RECEIVED",
"idempotencyKey": "8f7e6d5c-4b3a-4c1d-8e9f-8a7b6c5d4e3f",
"sender": "+12015550123",
"receivedAt": "2026-09-28T09:14:03.000Z"
}| Field | Type | Description |
|---|---|---|
sender | string | Number that sent the SMS, as the phone reports it. It can be a short code or a text sender name. |
receivedAt | string | ISO 8601 time when the phone received the SMS. |
A phone that was offline uploads its messages later. If the upload is more than 48 hours after receivedAt, textbee stores the message but does not send this event. The messages endpoint still returns it.
MESSAGE_SENT
Fires when the phone reports that it sent the message and the carrier accepted it. It does not confirm delivery to the recipient's handset.
{
"smsId": "66f7c3a9b1c2d3e4f5a6b7c8",
"message": "Your order has shipped.",
"deviceId": "664a9b8cd0e1f2a3b4c5d6e7",
"webhookSubscriptionId": "664a9b8cd0e1f2a3b4c5d6e8",
"webhookEvent": "MESSAGE_SENT",
"idempotencyKey": "2b4d6f8a-1c3e-4a5b-9d7f-0e2c4a6b8d1f",
"smsBatchId": "66f7c3a8b1c2d3e4f5a6b7c0",
"status": "sent",
"recipient": "+12015550124",
"sentAt": "2026-09-28T10:02:17.000Z"
}| Field | Type | Description |
|---|---|---|
smsBatchId | string | Batch ID returned by the send request. |
status | string | sent |
recipient | string | Number the message was sent to. |
sentAt | string | ISO 8601 time when the phone reported the message as sent. |
MESSAGE_DELIVERED
Fires when the carrier sends a delivery report to the phone. Carriers do not always send delivery reports. A message can reach the recipient without this event.
{
"smsId": "66f7c3a9b1c2d3e4f5a6b7c8",
"message": "Your order has shipped.",
"deviceId": "664a9b8cd0e1f2a3b4c5d6e7",
"webhookSubscriptionId": "664a9b8cd0e1f2a3b4c5d6e8",
"webhookEvent": "MESSAGE_DELIVERED",
"idempotencyKey": "5c7e9a1b-3d5f-4b6c-8e0a-2d4f6a8c0e3b",
"smsBatchId": "66f7c3a8b1c2d3e4f5a6b7c0",
"status": "delivered",
"recipient": "+12015550124",
"sentAt": "2026-09-28T10:02:17.000Z",
"deliveredAt": "2026-09-28T10:02:21.000Z"
}| Field | Type | Description |
|---|---|---|
smsBatchId | string | Batch ID returned by the send request. |
status | string | delivered |
recipient | string | Number the message was sent to. |
sentAt | string | ISO 8601 time when the phone reported the message as sent. |
deliveredAt | string | ISO 8601 time when the delivery report arrived. |
MESSAGE_FAILED
Fires when a message fails. The phone reports a failure when Android or the carrier rejects the send. textbee also fires this event when it cannot push the message to the phone.
{
"smsId": "66f7c4d0e1f2a3b4c5d6e7f8",
"message": "Your code is 482913",
"deviceId": "664a9b8cd0e1f2a3b4c5d6e7",
"webhookSubscriptionId": "664a9b8cd0e1f2a3b4c5d6e8",
"webhookEvent": "MESSAGE_FAILED",
"idempotencyKey": "9e1a3c5b-7d9f-4e2a-b4c6-8f0a2c4e6b8d",
"smsBatchId": "66f7c4cfe1f2a3b4c5d6e7f0",
"status": "failed",
"recipient": "+12015550125",
"errorCode": "4",
"errorMessage": "No cellular service. Check signal and try again when you have coverage.",
"failedAt": "2026-09-28T10:05:42.000Z"
}| Field | Type | Description |
|---|---|---|
smsBatchId | string | Batch ID returned by the send request. |
status | string | failed |
recipient | string | Number the message was sent to. |
errorCode | string | Failure code. For a failure on the phone, this is the Android send result code. |
errorMessage | string | Reason for the failure in plain words, with a suggested fix. |
failedAt | string | ISO 8601 time of the failure. |
failed is a final state. textbee does not resend a failed message. To try again, send a new message. Delivery status and message states explains the failure codes, and Messages not sending lists the fixes.
UNKNOWN_STATE
Fires when the phone reports a status that textbee does not map to sent, delivered or failed. The most common case is a delivery report that the carrier cancels or marks as failed. The message can still have reached the recipient.
{
"smsId": "66f7c3a9b1c2d3e4f5a6b7c9",
"message": "Your order has shipped.",
"deviceId": "664a9b8cd0e1f2a3b4c5d6e7",
"webhookSubscriptionId": "664a9b8cd0e1f2a3b4c5d6e8",
"webhookEvent": "UNKNOWN_STATE",
"idempotencyKey": "4a6c8e0b-2d4f-4a1c-9e3b-5d7f9a1c3e5b",
"smsBatchId": "66f7c3a8b1c2d3e4f5a6b7c0",
"status": "delivery_failed",
"recipient": "+12015550126"
}| Field | Type | Description |
|---|---|---|
smsBatchId | string | Batch ID returned by the send request. |
status | string | The status the phone reported, in lowercase. |
recipient | string | Number the message was sent to. |
Log this event and review it by hand. Do not treat it as a failure without a check.
SMS_STATUS_UPDATED
SMS_STATUS_UPDATED is a valid value in a subscription's events list, so the API accepts it. It is reserved: textbee does not emit it today. To track status changes, select MESSAGE_SENT, MESSAGE_DELIVERED, MESSAGE_FAILED and UNKNOWN_STATE.
Ordering and duplicates
textbee does not guarantee the order of events. A retry of one event can arrive after a later event for the same message. For example, MESSAGE_DELIVERED can arrive before MESSAGE_SENT when the first attempt of MESSAGE_SENT failed.
Follow these rules:
- Deduplicate on
idempotencyKey. Every retry of one delivery carries the same key. - Store the latest known state per
smsId. Do not move a message back to an earlier state.deliveredandfailedare final. - Compare timestamps (
sentAt,deliveredAt,failedAt) when two events disagree. - In rare cases the phone reports the same status twice, and textbee sends a second event with a new
idempotencyKey. Rule 2 makes this harmless.
To read the current state of one message at any time, call GET /gateway/devices/{id}/sms/{smsId}.
Which events to select
| Use case | Events |
|---|---|
| Inbox, two-way chat, keyword replies | MESSAGE_RECEIVED |
| Delivery tracking | MESSAGE_SENT, MESSAGE_DELIVERED, MESSAGE_FAILED, UNKNOWN_STATE |
| Failure alerts | MESSAGE_FAILED |
| Full audit log | All five events that fire today |
Each selected event adds deliveries. A bulk send produces one delivery per recipient for each selected status event. Select only what your code uses.
Frequently asked questions
Does every sent message produce MESSAGE_DELIVERED?
No. Carriers do not always send delivery reports. Treat MESSAGE_SENT as "left the phone" and MESSAGE_DELIVERED as a bonus when the carrier confirms.
Does the payload include the device name?
No. The payload has deviceId only. Call GET /gateway/devices/{id} to read the name and other device fields. See Managing devices.
Why is my webhook missing an event that the dashboard shows?
Check that the subscription selects that event, and that the subscription is active. Then check the delivery log under Webhooks > Deliveries for the httpStatusCode your endpoint returned.
Are MESSAGE_SENT and MESSAGE_DELIVERED sent for scheduled messages?
Yes. A scheduled message produces the same events when the phone sends it. See Scheduled messages.
Next steps
- Webhooks: create a subscription, verify signatures, retries
- Delivery status and message states: every status and timestamp
- Receiving SMS: turn on forwarding on the phone
- Message history and polling: read the same messages by API