Skip to main content

Webhooks

Webhooks let you receive real-time HTTP notifications when message events occur -- incoming messages, delivery confirmations, and failures.

Event types

EventDescription
message.receivedAn inbound iMessage was received on your connected number
message.sentAn outbound message was successfully sent from your iPhone
message.deliveredA sent message was confirmed as delivered. This functionality is working only with Standard plan and higher.
message.failedA message failed to send

Create a webhook

POST /v1/webhooks

Required permission: webhooks:manage

Request body

FieldTypeRequiredDescription
urlstringYesHTTPS URL to receive webhook events
eventsstring[]YesArray of event types to subscribe to
phone_numberstringNoFilter events to a specific phone number

Example

curl -X POST https://api.texting.blue/v1/webhooks \
-H "Content-Type: application/json" \
-H "x-api-key: tb_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-d '{
"url": "https://yourapp.com/webhook",
"events": ["message.received", "message.sent", "message.failed"]
}'

Response

Status: 201 Created

{
"id": "wh_xxxxxxxxxxxx",
"url": "https://yourapp.com/webhook",
"events": ["message.received", "message.sent", "message.failed"],
"phone_number": null,
"secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"active": true,
"created_at": "2026-02-07T12:00:00Z"
}
warning

Save the secret value immediately. It's only shown when the webhook is created and is required for signature verification.


Webhook payload

When an event fires, Texting Blue sends an HTTP POST request to your webhook URL.

Headers

HeaderDescription
Content-Typeapplication/json
x-textingblue-signatureHMAC-SHA256 signature of the request body
x-textingblue-eventThe event type (e.g., message.received)

Body

{
"id": "evt_xxxxxxxxxxxx",
"type": "message.received",
"timestamp": "2026-02-07T12:00:00Z",
"data": {
"id": "msg_xxxxxxxxxxxx",
"from": "+14155551234",
"to": "+14155559876",
"content": "Hey, got your message!",
"media_url": null,
"received_at": "2026-02-07T12:00:00Z"
}
}

Signature verification

Every webhook request includes an x-textingblue-signature header containing an HMAC-SHA256 signature. Always verify this signature to ensure the request came from Texting Blue.

The signature format is: sha256={hex_digest}

import crypto from 'crypto';

function verifyWebhookSignature(body, signature, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(body)
.digest('hex');

return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}

// In your Express handler:
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-textingblue-signature'];
const isValid = verifyWebhookSignature(req.body, signature, WEBHOOK_SECRET);

if (!isValid) {
return res.status(401).send('Invalid signature');
}

const event = JSON.parse(req.body);
// Process the event...
res.json({ received: true });
});

List webhooks

GET /v1/webhooks

Returns all webhooks for your team.

curl https://api.texting.blue/v1/webhooks \
-H "x-api-key: tb_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Response

{
"webhooks": [
{
"id": "wh_xxxxxxxxxxxx",
"url": "https://yourapp.com/webhook",
"events": ["message.received", "message.sent"],
"active": true,
"failure_count": 0,
"last_triggered_at": "2026-02-07T11:55:00Z",
"created_at": "2026-02-01T10:00:00Z"
}
]
}

Update a webhook

PUT /v1/webhooks/:id

Update the URL, events, or active status of a webhook.

Request body

FieldTypeRequiredDescription
urlstringNoNew webhook URL
eventsstring[]NoNew event types to subscribe to
activebooleanNoEnable or disable the webhook
curl -X PUT https://api.texting.blue/v1/webhooks/wh_xxxxxxxxxxxx \
-H "Content-Type: application/json" \
-H "x-api-key: tb_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-d '{"active": false}'

Delete a webhook

DELETE /v1/webhooks/:id

Permanently remove a webhook.

curl -X DELETE https://api.texting.blue/v1/webhooks/wh_xxxxxxxxxxxx \
-H "x-api-key: tb_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Response

{
"deleted": true
}

Retry behavior

If your webhook endpoint returns an error (HTTP status >= 400) or times out, Texting Blue retries the delivery:

  • Attempt 1: Immediate
  • Attempt 2: After 10 seconds
  • Attempt 3: After 60 seconds
  • Attempt 4: After 300 seconds (5 minutes)

After 10 consecutive failures across all deliveries, the webhook is automatically disabled. You'll need to re-enable it manually via the API or dashboard after fixing the issue.

Best practices

  • Always verify webhook signatures before processing events
  • Return a 200 response quickly (within 5 seconds). Do heavy processing asynchronously.
  • Use a unique URL path per webhook for easier debugging
  • Monitor your webhook's failure_count in the dashboard
  • Handle duplicate events gracefully (use the id field for deduplication)

Next steps