Skip to content

Latest commit

 

History

History
494 lines (379 loc) · 11.4 KB

File metadata and controls

494 lines (379 loc) · 11.4 KB

Webhooks

Webhooks let you receive real-time notifications when events occur in your Texting Blue account. Instead of polling the API, configure a webhook URL and Texting Blue will send an HTTP POST request to your server whenever a subscribed event fires.

Event Types

Event Description
message.received An inbound iMessage was received on one of your numbers.
message.sent An outbound message was successfully sent by your device.
message.delivered An outbound message was confirmed delivered to the recipient.
message.failed An outbound message failed to deliver.

Webhook Payload Format

Every webhook delivery is an HTTP POST with a JSON body:

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

Delivery Headers

Header Description
Content-Type application/json
x-textingblue-signature HMAC-SHA256 signature for verification. Format: sha256={hex_digest}.
x-textingblue-event The event type (e.g., message.received).

Retry Behavior

If your endpoint does not return a 2xx response, Texting Blue retries with the following schedule:

Attempt Delay
1st retry Immediate
2nd retry 10 seconds
3rd retry 60 seconds
4th retry 300 seconds (5 minutes)

After 10 consecutive failures, the webhook is automatically disabled. You can re-enable it by updating the webhook's active field to true.


Create a Webhook

POST /v1/webhooks

Permission: webhooks:manage

Register a new webhook endpoint. The response includes a secret field -- save it immediately, as it is only shown once. You will use this secret to verify webhook signatures.

Request Body

Field Type Required Description
url string Yes The HTTPS URL to receive webhook payloads.
events string[] Yes List of event types to subscribe to.
phone_number string No Filter events to a specific phone number (E.164). If omitted, receives events for all numbers.

Response -- 201 Created

{
  "id": "whk_xxxxxxxxxxxx",
  "url": "https://example.com/webhooks/texting-blue",
  "events": ["message.received", "message.failed"],
  "phone_number": null,
  "active": true,
  "secret": "whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
  "failure_count": 0,
  "last_triggered_at": null,
  "created_at": "2026-02-07T12:00:00Z"
}

Important: The secret field is only included in the create response. Store it securely. If you lose it, delete the webhook and create a new one.

Examples

cURL

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

Node.js

const response = await fetch("https://api.texting.blue/v1/webhooks", {
  method: "POST",
  headers: {
    "x-api-key": "tb_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    url: "https://example.com/webhooks/texting-blue",
    events: ["message.received", "message.failed"],
  }),
});

const webhook = await response.json();
// Save webhook.secret securely -- it is only shown once
console.log(webhook.secret); // "whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"

Python

import requests

response = requests.post(
    "https://api.texting.blue/v1/webhooks",
    headers={
        "x-api-key": "tb_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
        "Content-Type": "application/json",
    },
    json={
        "url": "https://example.com/webhooks/texting-blue",
        "events": ["message.received", "message.failed"],
    },
)

webhook = response.json()
# Save webhook["secret"] securely -- it is only shown once
print(webhook["secret"])  # "whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"

PHP

$ch = curl_init("https://api.texting.blue/v1/webhooks");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "x-api-key: tb_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
    "Content-Type: application/json",
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
    "url" => "https://example.com/webhooks/texting-blue",
    "events" => ["message.received", "message.failed"],
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);
curl_close($ch);

$webhook = json_decode($response, true);
// Save $webhook["secret"] securely -- it is only shown once
echo $webhook["secret"]; // "whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"

List Webhooks

GET /v1/webhooks

Permission: webhooks:manage

Returns all webhooks registered on your account. The secret field is not included in list responses.

Response -- 200 OK

{
  "webhooks": [
    {
      "id": "whk_xxxxxxxxxxxx",
      "url": "https://example.com/webhooks/texting-blue",
      "events": ["message.received", "message.failed"],
      "active": true,
      "failure_count": 0,
      "last_triggered_at": "2026-02-07T11:55:00Z",
      "created_at": "2026-02-07T12:00:00Z"
    }
  ]
}

Examples

cURL

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

Node.js

const response = await fetch("https://api.texting.blue/v1/webhooks", {
  headers: {
    "x-api-key": "tb_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
  },
});

const { webhooks } = await response.json();

Python

import requests

response = requests.get(
    "https://api.texting.blue/v1/webhooks",
    headers={"x-api-key": "tb_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"},
)

webhooks = response.json()["webhooks"]

PHP

$ch = curl_init("https://api.texting.blue/v1/webhooks");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "x-api-key: tb_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);
curl_close($ch);

$webhooks = json_decode($response, true)["webhooks"];

Get a Webhook

GET /v1/webhooks/:id

Permission: webhooks:manage

Retrieve details of a single webhook.

Response -- 200 OK

{
  "id": "whk_xxxxxxxxxxxx",
  "url": "https://example.com/webhooks/texting-blue",
  "events": ["message.received", "message.failed"],
  "active": true,
  "failure_count": 0,
  "last_triggered_at": "2026-02-07T11:55:00Z",
  "created_at": "2026-02-07T12:00:00Z"
}

Examples

cURL

curl -X GET https://api.texting.blue/v1/webhooks/whk_xxxxxxxxxxxx \
  -H "x-api-key: tb_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"

Update a Webhook

PUT /v1/webhooks/:id

Permission: webhooks:manage

Update a webhook's URL, events, or active status. Only include the fields you want to change.

Request Body

Field Type Required Description
url string No New HTTPS URL for the webhook.
events string[] No New list of event types to subscribe to.
active boolean No Set to false to disable or true to re-enable.

Response -- 200 OK

Returns the updated webhook object.

{
  "id": "whk_xxxxxxxxxxxx",
  "url": "https://example.com/webhooks/v2",
  "events": ["message.received", "message.sent", "message.delivered", "message.failed"],
  "active": true,
  "failure_count": 0,
  "last_triggered_at": "2026-02-07T11:55:00Z",
  "created_at": "2026-02-07T12:00:00Z"
}

Examples

cURL

curl -X PUT https://api.texting.blue/v1/webhooks/whk_xxxxxxxxxxxx \
  -H "x-api-key: tb_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/webhooks/v2",
    "events": ["message.received", "message.sent", "message.delivered", "message.failed"]
  }'

Node.js

const response = await fetch(
  "https://api.texting.blue/v1/webhooks/whk_xxxxxxxxxxxx",
  {
    method: "PUT",
    headers: {
      "x-api-key": "tb_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      events: [
        "message.received",
        "message.sent",
        "message.delivered",
        "message.failed",
      ],
    }),
  }
);

const webhook = await response.json();

Python

import requests

response = requests.put(
    "https://api.texting.blue/v1/webhooks/whk_xxxxxxxxxxxx",
    headers={
        "x-api-key": "tb_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
        "Content-Type": "application/json",
    },
    json={
        "events": [
            "message.received",
            "message.sent",
            "message.delivered",
            "message.failed",
        ]
    },
)

webhook = response.json()

PHP

$ch = curl_init("https://api.texting.blue/v1/webhooks/whk_xxxxxxxxxxxx");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "x-api-key: tb_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
    "Content-Type: application/json",
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
    "events" => [
        "message.received",
        "message.sent",
        "message.delivered",
        "message.failed",
    ],
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);
curl_close($ch);

$webhook = json_decode($response, true);

Re-enabling a Disabled Webhook

If a webhook is automatically disabled after 10 consecutive failures, fix the underlying issue on your server and then re-enable it:

curl -X PUT https://api.texting.blue/v1/webhooks/whk_xxxxxxxxxxxx \
  -H "x-api-key: tb_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6" \
  -H "Content-Type: application/json" \
  -d '{ "active": true }'

Delete a Webhook

DELETE /v1/webhooks/:id

Permission: webhooks:manage

Permanently remove a webhook. Any in-flight deliveries will still be attempted, but no new events will be sent.

Response -- 200 OK

{
  "deleted": true
}

Examples

cURL

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

Node.js

const response = await fetch(
  "https://api.texting.blue/v1/webhooks/whk_xxxxxxxxxxxx",
  {
    method: "DELETE",
    headers: {
      "x-api-key": "tb_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
    },
  }
);

const { deleted } = await response.json();

Python

import requests

response = requests.delete(
    "https://api.texting.blue/v1/webhooks/whk_xxxxxxxxxxxx",
    headers={"x-api-key": "tb_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"},
)

print(response.json()["deleted"])  # True

PHP

$ch = curl_init("https://api.texting.blue/v1/webhooks/whk_xxxxxxxxxxxx");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "x-api-key: tb_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);
curl_close($ch);

$data = json_decode($response, true);
echo $data["deleted"]; // 1 (true)