Webhooks
Real-time event notifications | iMessage For Business
Webhooks allow you to receive real-time notifications when events occur in your Sendblue account. You can configure multiple webhooks for different event types and manage them via our API.
In this documentation, we will cover how to:
- Understand webhook types and formats
- Set up and manage webhooks
- Secure your webhook endpoints
- Handle webhook events
Webhook Types
Section titled “Webhook Types”The following webhook types are supported:
| Type | Description |
|---|---|
| receive | Triggered when you receive an inbound message |
| outbound | Triggered when an outbound message is sent |
| typing_indicator | Triggered when a contact starts or stops typing |
| call_log | Triggered when a call log is received |
| line_blocked | Triggered when a line is blocked |
| line_assigned | Triggered when a line is assigned |
| contact_created | Triggered when a contact is created |
Webhook Format
Section titled “Webhook Format”Webhooks can be specified in two formats:
- Simple URL string:
"https://example.com/webhook" - Object with URL and secret:
{ "url": "https://example.com/webhook", "secret": "my-secret" }
Managing Webhooks
Section titled “Managing Webhooks”You can manage your webhooks using the following API endpoints:
- List all webhooks - Get all configured webhooks
- Create webhooks - Add new webhooks (appends to existing)
- Update webhooks - Replace all webhooks
- Delete webhooks - Remove specific webhooks
Listing Webhooks
Section titled “Listing Webhooks”Retrieve all webhooks configured for your account:
curl -X GET https://api.sendblue.co/api/account/webhooks \ -H "sb-api-key-id: YOUR_API_KEY" \ -H "sb-api-secret-key: YOUR_API_SECRET"Response:
{ "status": "OK", "webhooks": { "receive": [ "https://example.com/webhook1", { "url": "https://example.com/webhook2", "secret": "webhook-secret" } ], "call_log": [], "line_blocked": [], "line_assigned": [], "outbound": [], "contact_created": ["https://example.com/contact-webhook"], "globalSecret": "global-secret" }}Adding Webhooks
Section titled “Adding Webhooks”Add new webhooks to your account. This endpoint appends to the existing webhook list:
curl -X POST https://api.sendblue.co/api/account/webhooks \ -H "sb-api-key-id: YOUR_API_KEY" \ -H "sb-api-secret-key: YOUR_API_SECRET" \ -H "Content-Type: application/json" \ -d '{ "webhooks": [ "https://example.com/new-webhook", { "url": "https://example.com/webhook-with-secret", "secret": "my-webhook-secret" } ], "type": "receive" }'Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| webhooks | array | Yes | Array of webhook URLs or webhook objects |
| type | string | No | Webhook type (default: receive) |
| globalSecret | string | No | Global secret to apply to all webhooks |
Replacing All Webhooks
Section titled “Replacing All Webhooks”Replace the entire webhook configuration for your account:
curl -X PUT https://api.sendblue.co/api/account/webhooks \ -H "sb-api-key-id: YOUR_API_KEY" \ -H "sb-api-secret-key: YOUR_API_SECRET" \ -H "Content-Type: application/json" \ -d '{ "webhooks": { "receive": ["https://example.com/webhook"], "call_log": ["https://example.com/call-webhook"], "contact_created": ["https://example.com/contact-webhook"], "globalSecret": "my-global-secret" } }'Deleting Webhooks
Section titled “Deleting Webhooks”Remove specific webhooks from your account:
curl -X DELETE https://api.sendblue.co/api/account/webhooks \ -H "sb-api-key-id: YOUR_API_KEY" \ -H "sb-api-secret-key: YOUR_API_SECRET" \ -H "Content-Type: application/json" \ -d '{ "webhooks": ["https://example.com/webhook-to-delete"], "type": "receive" }'Webhook Security
Section titled “Webhook Security”Sendblue supports multiple ways to secure your webhook endpoints:
- Per-webhook secret: Set a
secreton individual webhook objects - Global secret: Set a
globalSecretthat applies to all webhooks - Legacy secret: The
secretfield at the root level (older format)
When you configure a secret, Sendblue will include it in the webhook request headers, allowing you to verify that the request is genuinely from Sendblue.
// Example: Setting up a webhook with a secretconst response = await fetch("https://api.sendblue.co/api/account/webhooks", { method: "POST", headers: { "sb-api-key-id": "YOUR_API_KEY", "sb-api-secret-key": "YOUR_API_SECRET", "Content-Type": "application/json", }, body: JSON.stringify({ webhooks: [ { url: "https://myapp.com/webhooks/sendblue", secret: "my-secure-secret-123", }, ], type: "receive", }),});Handling Webhooks
Section titled “Handling Webhooks”Inbound Message Webhook
Section titled “Inbound Message Webhook”When you receive an inbound message, Sendblue will POST to your configured receive webhook with the following payload:
{ "accountEmail": "your-account-email", "content": "Hello!", "is_outbound": false, "status": "RECEIVED", "error_code": null, "error_message": null, "error_reason": null, "message_handle": "99DCC379-DD76-4712-BA65-11EFB33B8CD6", "date_sent": "2025-12-12T15:41:20.932Z", "date_updated": "2025-12-12T15:41:20.995Z", "from_number": "+19998887777", "number": "+19998887777", "to_number": "+15122164639", "was_downgraded": null, "plan": "dedicated", "media_url": "", "message_type": "message", "group_id": "", "participants": ["+19998887777", "+15122164639"], "send_style": "", "opted_out": false, "error_detail": null, "sendblue_number": "+15122164639", "service": "iMessage", "group_display_name": null}Outbound Message Webhook
Section titled “Outbound Message Webhook”For outbound messages, you can use the status_callback parameter when sending a message, or configure an outbound webhook to receive all outbound message status updates:
{ "accountEmail": "your-account-email", "content": "Hello world!", "is_outbound": true, "status": "SENT", "error_code": null, "error_message": null, "error_reason": null, "message_handle": "5a17319e-cbcf-443e-897e-d8b0c04b1b09", "date_sent": "2025-12-12T15:35:35.410Z", "date_updated": "2025-12-12T15:35:35.410Z", "from_number": "+18649820355", "number": "+19998887777", "to_number": "+19998887777", "was_downgraded": null, "plan": "dedicated", "media_url": "", "message_type": "message", "group_id": "", "participants": [], "send_style": "", "opted_out": false, "error_detail": null, "sendblue_number": null, "service": "iMessage", "group_display_name": null}Call Log Webhook
Section titled “Call Log Webhook”When a call is logged (inbound or outbound), Sendblue will POST to your configured call_log webhook with the following payload:
{ "event_type": "call_log", "call_id": "cs_abc123def456", "from_number": "+15551234567", "to_number": "+15559876543", "direction": "inbound", "status": "COMPLETED", "duration": 120, "provider": "twilio", "company_id": "550e8400-e29b-41d4-a716-446655440000", "contact_id": "660e8400-e29b-41d4-a716-446655440000", "recording_url": "https://api.twilio.com/recordings/RE1234.mp3", "transcript": "Hello, how can I help you today?", "metadata": {}, "created_at": "2026-02-07T12:00:00Z", "disposition": "connected", "origin": "DASHBOARD",}Call Log Payload Fields
Section titled “Call Log Payload Fields”| Field | Type | Description |
|---|---|---|
| event_type | string | Always "call_log" |
| call_id | string | Sendblue call session identifier |
| from_number | string | E.164 formatted caller phone number |
| to_number | string | E.164 formatted called phone number |
| direction | string | "inbound" or "outbound" |
| status | string | Final status of the call (e.g. COMPLETED, CANCELLED) |
| duration | integer | Call duration in seconds (null if not available) |
| provider | string | Telephony provider ("twilio" or "facetime") |
| company_id | string | UUID of the company |
| contact_id | string | UUID of the contact (null if not linked) |
| recording_url | string | URL of the call recording (null if not recorded) |
| transcript | string | Call transcript (null if not transcribed) |
| metadata | object | Additional metadata associated with the call |
| created_at | string | ISO 8601 timestamp of when the call was created |
| disposition | string | Call disposition (e.g. connected, not_answered, voicemail) |
| origin | string | Where the call originated from (e.g. DASHBOARD) |
| sent_by | string | Identifier of the user who initiated the call |
Message Webhook Payload Fields
Section titled “Message Webhook Payload Fields”| Field | Type | Description |
|---|---|---|
| accountEmail | string | Associated account email |
| content | string | Message content |
| is_outbound | boolean | True if message is sent, false if message is received |
| status | string | The current status of the message |
| error_code | int | Error code (null if no error) |
| error_message | string | Descriptive error message (null if no error) |
| error_reason | string | Additional error context (null if no error) |
| error_detail | string | Detailed error information (null if no error) |
| message_handle | string | Sendblue message handle |
| date_sent | string | ISO 8601 formatted date string of when message was created |
| date_updated | string | ISO 8601 formatted date string of when message was last updated |
| from_number | string | E.164 formatted phone number of the message dispatcher |
| number | string | E.164 formatted phone number of your end-user |
| to_number | string | E.164 formatted phone number of the message recipient |
| was_downgraded | boolean | True if the end user does not support iMessage, null otherwise |
| plan | string | Value of the Sendblue account plan |
| media_url | string | A CDN link to any media attached to the message |
| message_type | string | Type of message (e.g., “message”) |
| group_id | string | Group identifier (empty string for non-group messages) |
| participants | array | Array of participant phone numbers |
| send_style | string | Expressive message style if used (empty string if none) |
| opted_out | boolean | True if the recipient has opted out |
| sendblue_number | string | The Sendblue phone number used |
| service | string | The messaging service used (e.g., “iMessage”, “SMS”) |
| group_display_name | string | Display name for group chats (null for non-group messages) |
Best Practices
Section titled “Best Practices”1. Use HTTPS Only
Section titled “1. Use HTTPS Only”All webhook URLs must use HTTPS to ensure secure communication.
2. Implement Idempotency
Section titled “2. Implement Idempotency”Your webhook endpoints should be idempotent, as they may receive duplicate events. Use the message_handle field to deduplicate events.
// Example: Idempotent webhook handlerconst processedMessages = new Set();
app.post("/webhook", (req, res) => { const { message_handle } = req.body;
if (processedMessages.has(message_handle)) { return res.status(200).send("Already processed"); }
processedMessages.add(message_handle); // Process the webhook... res.status(200).send("OK");});3. Verify Webhook Signatures
Section titled “3. Verify Webhook Signatures”Always verify that webhook requests are coming from Sendblue by checking the secret in the request headers.
4. Return Appropriate Status Codes
Section titled “4. Return Appropriate Status Codes”- Return 200-299 for successful processing
- Return 410 Gone if you want Sendblue to automatically remove the webhook
5. Handle Errors Gracefully
Section titled “5. Handle Errors Gracefully”Implement proper error handling and logging in your webhook endpoints to troubleshoot issues.
Error Responses
Section titled “Error Responses”401 Unauthorized
Section titled “401 Unauthorized”{ "status": "ERROR", "message": "Unauthorized"}Authentication failed. Check your API credentials.
400 Bad Request
Section titled “400 Bad Request”{ "status": "ERROR", "message": "Missing or invalid webhooks array"}The request body is malformed or missing required fields.
500 Internal Server Error
Section titled “500 Internal Server Error”{ "status": "ERROR", "message": "Error message details"}An internal error occurred. Contact support if this persists.
Examples
Section titled “Examples”Example 1: Basic Webhook Setup
Section titled “Example 1: Basic Webhook Setup”// Add a simple webhook for receiving messagesconst response = await fetch("https://api.sendblue.co/api/account/webhooks", { method: "POST", headers: { "sb-api-key-id": "YOUR_API_KEY", "sb-api-secret-key": "YOUR_API_SECRET", "Content-Type": "application/json", }, body: JSON.stringify({ webhooks: ["https://myapp.com/webhooks/sendblue"], type: "receive", }),});
const data = await response.json();console.log(data);Example 2: Multi-Type Webhook Configuration
Section titled “Example 2: Multi-Type Webhook Configuration”// Configure webhooks for multiple event typesconst response = await fetch("https://api.sendblue.co/api/account/webhooks", { method: "PUT", headers: { "sb-api-key-id": "YOUR_API_KEY", "sb-api-secret-key": "YOUR_API_SECRET", "Content-Type": "application/json", }, body: JSON.stringify({ webhooks: { receive: ["https://myapp.com/webhooks/receive"], outbound: ["https://myapp.com/webhooks/outbound"], call_log: ["https://myapp.com/webhooks/calls"], contact_created: ["https://myapp.com/webhooks/contacts"], globalSecret: "my-global-secret", }, }),});Example 3: Express.js Webhook Handler
Section titled “Example 3: Express.js Webhook Handler”const express = require("express");const app = express();
app.use(express.json());
app.post("/webhooks/sendblue", (req, res) => { const { content, from_number, message_handle, is_outbound, status } = req.body;
console.log(`New message from ${from_number}: ${content}`); console.log(`Message handle: ${message_handle}`); console.log(`Status: ${status}`);
// Process the message here // ...
// Always respond with 200 to acknowledge receipt res.status(200).send("OK");});
app.listen(3000, () => { console.log("Webhook server running on port 3000");});Retry Policy
Section titled “Retry Policy”Sendblue retries webhook delivery up to 3 times if your endpoint returns a 5xx server error. Sendblue waits 45 seconds for a response from your endpoint. If no response is received within that window, the delivery is treated as failed and will be retried.
Ensure your endpoint is idempotent to handle potential duplicate deliveries during retries. Use the message_handle field to deduplicate events.
Limitations
Section titled “Limitations”- Account-level only: Webhook URLs are configured at the account level. All lines on a single account share the same webhook endpoints. You cannot set a different webhook per phone number.
- No default status callback: There is no way to set a global default
status_callbackwebhook. The status callback URL must be specified on each individual message send request. See Sending messages. - Contact update webhooks: Webhook events for contact updates (e.g., tags and notes) are not currently available. This is being evaluated for a future release.
- Webhook error logging: Dedicated webhook error logging is coming soon and will be accessible for internal tracking and debugging.
Additional Notes
Section titled “Additional Notes”- Webhook URLs are automatically validated to ensure they are valid HTTPS URLs
- The API maintains backward compatibility with legacy webhook formats
- The
receivewebhook type is the most commonly used for inbound messages - You can configure multiple webhooks for the same event type for redundancy