Skip to content
Get Started
Getting Started

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:

  1. Understand webhook types and formats
  2. Set up and manage webhooks
  3. Secure your webhook endpoints
  4. Handle webhook events

The following webhook types are supported:

TypeDescription
receiveTriggered when you receive an inbound message
outboundTriggered when an outbound message is sent
typing_indicatorTriggered when a contact starts or stops typing
call_logTriggered when a call log is received
line_blockedTriggered when a line is blocked
line_assignedTriggered when a line is assigned
contact_createdTriggered when a contact is created

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" }

You can manage your webhooks using the following API endpoints:

Retrieve all webhooks configured for your account:

Terminal window
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"
}
}

Add new webhooks to your account. This endpoint appends to the existing webhook list:

Terminal window
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:

ParameterTypeRequiredDescription
webhooksarrayYesArray of webhook URLs or webhook objects
typestringNoWebhook type (default: receive)
globalSecretstringNoGlobal secret to apply to all webhooks

Replace the entire webhook configuration for your account:

Terminal window
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"
}
}'

Remove specific webhooks from your account:

Terminal window
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"
}'

Sendblue supports multiple ways to secure your webhook endpoints:

  • Per-webhook secret: Set a secret on individual webhook objects
  • Global secret: Set a globalSecret that applies to all webhooks
  • Legacy secret: The secret field 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 secret
const 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",
}),
});

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
}

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
}

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",
"sent_by": "[email protected]"
}
FieldTypeDescription
event_typestringAlways "call_log"
call_idstringSendblue call session identifier
from_numberstringE.164 formatted caller phone number
to_numberstringE.164 formatted called phone number
directionstring"inbound" or "outbound"
statusstringFinal status of the call (e.g. COMPLETED, CANCELLED)
durationintegerCall duration in seconds (null if not available)
providerstringTelephony provider ("twilio" or "facetime")
company_idstringUUID of the company
contact_idstringUUID of the contact (null if not linked)
recording_urlstringURL of the call recording (null if not recorded)
transcriptstringCall transcript (null if not transcribed)
metadataobjectAdditional metadata associated with the call
created_atstringISO 8601 timestamp of when the call was created
dispositionstringCall disposition (e.g. connected, not_answered, voicemail)
originstringWhere the call originated from (e.g. DASHBOARD)
sent_bystringIdentifier of the user who initiated the call
FieldTypeDescription
accountEmailstringAssociated account email
contentstringMessage content
is_outboundbooleanTrue if message is sent, false if message is received
statusstringThe current status of the message
error_codeintError code (null if no error)
error_messagestringDescriptive error message (null if no error)
error_reasonstringAdditional error context (null if no error)
error_detailstringDetailed error information (null if no error)
message_handlestringSendblue message handle
date_sentstringISO 8601 formatted date string of when message was created
date_updatedstringISO 8601 formatted date string of when message was last updated
from_numberstringE.164 formatted phone number of the message dispatcher
numberstringE.164 formatted phone number of your end-user
to_numberstringE.164 formatted phone number of the message recipient
was_downgradedbooleanTrue if the end user does not support iMessage, null otherwise
planstringValue of the Sendblue account plan
media_urlstringA CDN link to any media attached to the message
message_typestringType of message (e.g., “message”)
group_idstringGroup identifier (empty string for non-group messages)
participantsarrayArray of participant phone numbers
send_stylestringExpressive message style if used (empty string if none)
opted_outbooleanTrue if the recipient has opted out
sendblue_numberstringThe Sendblue phone number used
servicestringThe messaging service used (e.g., “iMessage”, “SMS”)
group_display_namestringDisplay name for group chats (null for non-group messages)

All webhook URLs must use HTTPS to ensure secure communication.

Your webhook endpoints should be idempotent, as they may receive duplicate events. Use the message_handle field to deduplicate events.

// Example: Idempotent webhook handler
const 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");
});

Always verify that webhook requests are coming from Sendblue by checking the secret in the request headers.

  • Return 200-299 for successful processing
  • Return 410 Gone if you want Sendblue to automatically remove the webhook

Implement proper error handling and logging in your webhook endpoints to troubleshoot issues.

{
"status": "ERROR",
"message": "Unauthorized"
}

Authentication failed. Check your API credentials.

{
"status": "ERROR",
"message": "Missing or invalid webhooks array"
}

The request body is malformed or missing required fields.

{
"status": "ERROR",
"message": "Error message details"
}

An internal error occurred. Contact support if this persists.

// Add a simple webhook for receiving messages
const 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 types
const 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",
},
}),
});
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");
});

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.

  • 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_callback webhook. 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.
  • Webhook URLs are automatically validated to ensure they are valid HTTPS URLs
  • The API maintains backward compatibility with legacy webhook formats
  • The receive webhook type is the most commonly used for inbound messages
  • You can configure multiple webhooks for the same event type for redundancy