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 for outbound calls placed from the dashboard. Inbound calls do not trigger this webhook. Use inbound_call instead.
inbound_callTriggered when an inbound call to one of your Sendblue numbers ends. Fires for every call Sendblue routes for you, answered and missed alike, with the outcome in the payload.
line_blockedTriggered when a line is blocked
line_assignedTriggered when a line is assigned
contact_profileTriggered when a Name & Photo profile update completes or fails
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" }

Manage account webhook subscriptions in the Sendblue dashboard under Developer → Webhooks, or with the following API endpoints:

Retrieve all webhooks configured for your account:

Terminal window
curl -X GET https://api.sendblue.com/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": [],
"inbound_call": [],
"line_blocked": [],
"line_assigned": [],
"outbound": [],
"contact_profile": [],
"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.com/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.com/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_profile": ["https://example.com/contact-profile-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.com/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.com/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. This example payload is an inline reply:

{
"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,
"reply_to": {
"message_handle": "2F172CD8-C082-4A54-B9B3-D5B78CF4D0C3"
},
"thread_originator": {
"message_handle": "2F172CD8-C082-4A54-B9B3-D5B78CF4D0C3"
}
}

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. This example is an inline reply:

{
"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,
"reply_to": {
"message_handle": "99DCC379-DD76-4712-BA65-11EFB33B8CD6",
"part_index": 0
},
"thread_originator": {
"message_handle": "99DCC379-DD76-4712-BA65-11EFB33B8CD6"
}
}

Inline replies include reply_to and thread_originator objects. Each contains a message_handle; responses may also include server-reported part metadata when it is authoritative. These objects are omitted for messages outside a reply thread. See the inline replies guide for field semantics and send examples.

When an outbound call is placed from the Sendblue dashboard, Sendblue will POST to your configured call_log webhook with the following payload. Inbound calls are not logged to the call-log pipeline and do not trigger the call_log webhook.

{
"event_type": "call_log",
"call_id": "cs_abc123def456",
"from_number": "+15551234567",
"to_number": "+15559876543",
"direction": "outbound",
"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"outbound" for webhook deliveries
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

When someone calls one of your Sendblue numbers, Sendblue POSTs to your configured inbound_call webhook once the call ends. It fires for every call Sendblue routes for you. Forwarded to your forwarding number, or answered in the dashboard, same goes for missed ones. Use the status and answered fields to tell them apart, for example to follow up on missed calls.

{
"event_type": "inbound_call",
"event_id": "inbound-call-CA1234567890abcdef1234567890abcdef",
"accountEmail": "your-account-email",
"call_id": "CA1234567890abcdef1234567890abcdef",
"from_number": "+15551234567",
"to_number": "+15559876543",
"forwarded_to": "+15550001111",
"direction": "inbound",
"status": "NO_ANSWER",
"answered": false,
"duration": null,
"provider": "twilio",
"company_id": "550e8400-e29b-41d4-a716-446655440000",
"created_at": "2026-08-12T18:00:00.000Z",
"ended_at": "2026-08-12T18:00:35.000Z"
}
FieldTypeDescription
event_typestringAlways "inbound_call"
event_idstringUnique identifier for this event: use it to deduplicate deliveries
accountEmailstringIdentifier of the associated account. Usually an email address, but for accounts created through the dashboard it is the company name
call_idstringProvider call identifier for the inbound call
from_numberstringE.164 formatted number of the caller
to_numberstringE.164 formatted Sendblue number that was called
forwarded_tostringNumber the call was forwarded to (null if not forwarded)
directionstringAlways "inbound"
statusstringFinal outcome of the call: COMPLETED, NO_ANSWER, BUSY, FAILED, or CANCELLED
answeredbooleanConvenience flag: true when the call was answered (status is COMPLETED), false for missed calls
durationintegerTalk time in seconds (null when the call was never answered)
providerstringTelephony provider that carried the call ("twilio" or "telnyx")
company_idstringUUID of the company (null if not linked)
created_atstringISO 8601 timestamp of when the inbound call started
ended_atstringISO 8601 timestamp of when the final outcome was recorded. Normally within seconds of the call ending, but later when a provider callback was lost and the outcome had to be recovered

Subscribe to contact_profile before calling POST /api/v2/contact-sharing/profile to receive the final result of a profile update. The profile request returns immediately; the webhook is sent later when the update completes or fails.

Terminal window
curl -X POST "https://api.sendblue.com/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/webhooks/contact-profile"],
"type": "contact_profile"
}'

Successful update:

{
"event_type": "contact_profile.completed",
"event_id": "contact-profile:bc0371a7-b615-4d49-9e42-fec74da98a53:completed",
"request_id": "3f377d6e-2d36-45bb-b7ed-e87f0b7e792d",
"operation_id": "bc0371a7-b615-4d49-9e42-fec74da98a53",
"accountEmail": "your-account-email",
"from_number": "+14155551234",
"status": "completed",
"profile": {
"hasProfile": true,
"sharingEnabled": true,
"firstName": "Ada",
"lastName": "Lovelace",
"displayName": "Ada Lovelace",
"hasPhoto": true,
"publishRecordPresent": true
},
"error_code": null,
"error_message": null,
"requested_at": "2026-08-24T15:00:00.000Z",
"observed_at": "2026-08-24T15:01:00.000Z"
}
FieldTypeDescription
event_typestringcontact_profile.completed or contact_profile.failed
event_idstringStable event identifier. Use it to deduplicate deliveries.
request_idstringIdentifier for the accepted API request
operation_idstring or nullIdentifier for the profile update
accountEmailstringAccount associated with the Sendblue number
from_numberstringSendblue number whose profile was updated
statusstringcompleted or failed
profileobject or nullFinal profile state for a completed update; null for a failed update
error_codestring or nullMachine-readable failure code
error_messagestring or nullFailure description
requested_atstringISO 8601 time when the update was requested
observed_atstringISO 8601 time when the final result was observed

For failed updates, event_type is contact_profile.failed, status is failed, profile is null, and the error fields describe the failure.

Delivery is at least once. Deduplicate deliveries using event_id. Repeated requests for the same in-progress update may produce a single completion event. The event confirms that the sender’s profile was updated; it does not confirm when a recipient sees or accepts it.

Returning 410 Gone does not automatically remove a contact_profile subscription. Remove it with the webhook API when it is no longer needed.

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)
sender_emailstringEmail of the seat (user) that sent the message. Auto-populated when a seat_id is supplied on the send endpoint; null otherwise.
seat_idstringUUID of the seat that sent the message (null when no seat was provided).

All webhook URLs must use HTTPS to ensure secure communication.

Your webhook endpoints should be idempotent, as they may receive duplicate events. Use message_handle for message events and event_id for events that provide it.

// 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. This automatic removal does not apply to contact_profile webhooks.

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.com/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.com/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"],
inbound_call: ["https://myapp.com/webhooks/inbound-calls"],
contact_profile: ["https://myapp.com/webhooks/contact-profile"],
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 message_handle for message events and event_id for events that provide it.

  • 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.
  • Call log scope: The call_log webhook only fires for outbound calls made from the dashboard. Inbound calls trigger the inbound_call webhook instead.
  • Calls Sendblue never routes: an inbound call that is turned away before it reaches you produces no inbound_call webhook, for example, a caller on your block list, or a call arriving while your account has no forwarding number configured. Configure a forwarding number if you want missed calls reported.
  • 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