Skip to content
Get Started
Getting Started

Webhooks

Real-time event notifications - Sendblue

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/v2/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/v2/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/v2/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/v2/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/v2/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
}
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/v2/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/v2/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');
});
  • 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