Webhooks let Sahut send real-time HTTP POST notifications to your server when events occur — such as a new message arriving or a conversation being resolved. Use webhooks to trigger actions in your systems without polling the API.
How webhooks work
- You create a webhook endpoint on your server (a URL that accepts POST requests)
- You register that URL in Sahut
- When an event occurs, Sahut sends a POST request with a JSON payload to your URL
- Your server responds with
200 OK within 10 seconds
If your server doesn’t respond with 2xx within 10 seconds, Sahut retries delivery up to 5 times with exponential backoff (5s, 30s, 2m, 10m, 30m).
Registering a webhook
Via the dashboard
- Go to Settings → Integrations → Webhooks
- Click Tambah Webhook (Add Webhook)
- Enter your Endpoint URL
- Select the Events you want to receive
- Click Simpan (Save)
Via the API
Request body
The HTTPS URL to receive webhook events.
List of event types to subscribe to. See event types below.
Example request
curl -X POST https://api.sahut.id/v1/webhooks \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourserver.com/sahut-webhook",
"events": ["message.created", "conversation.resolved"]
}'
Example response
{
"data": {
"id": "wh_01HX6G4P8R0T2U",
"url": "https://yourserver.com/sahut-webhook",
"events": ["message.created", "conversation.resolved"],
"secret": "whsec_a1b2c3d4e5f6...",
"created_at": "2024-03-15T10:00:00Z"
}
}
Copy the secret value from the creation response. Sahut only shows it once. You use this secret to verify webhook signatures.
Delete a webhook
Event types
| Event | Triggered when |
|---|
conversation.created | A new conversation is created |
conversation.updated | A conversation’s status, assignee, or labels change |
conversation.resolved | A conversation is marked as resolved |
message.created | A new message is sent or received in any conversation |
contact.created | A new contact profile is created |
contact.updated | A contact’s details are updated |
All webhook events share the same envelope structure:
{
"event": "message.created",
"timestamp": "2024-03-15T08:30:00Z",
"workspace_id": "ws_01HX0Y6H0J2K4L",
"data": { }
}
Example: message.created payload
{
"event": "message.created",
"timestamp": "2024-03-15T08:30:00Z",
"workspace_id": "ws_01HX0Y6H0J2K4L",
"data": {
"message": {
"id": "msg_01HX5E2N6P8R0S",
"type": "incoming",
"content": "Halo, saya ingin bertanya tentang produk Anda",
"created_at": "2024-03-15T08:30:00Z"
},
"conversation": {
"id": "conv_01HX2B9K3M5N7P",
"status": "open"
},
"contact": {
"id": "ct_01HX3C0L4N6P8Q",
"name": "Budi Santoso",
"phone": "+628123456789"
},
"channel": {
"id": "ch_01HX1A8J2L4M6N",
"type": "whatsapp"
}
}
}
Verifying webhook signatures
Sahut signs every webhook request so you can verify it came from Sahut and wasn’t tampered with.
Each request includes a X-Sahut-Signature header containing an HMAC-SHA256 signature of the raw request body, signed with your webhook’s secret.
To verify the signature:
const crypto = require('crypto');
function verifyWebhook(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(`sha256=${expected}`)
);
}
// In your request handler:
app.post('/sahut-webhook', (req, res) => {
const rawBody = req.rawBody; // unparsed request body string
const signature = req.headers['x-sahut-signature'];
const secret = process.env.SAHUT_WEBHOOK_SECRET;
if (!verifyWebhook(rawBody, signature, secret)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(rawBody);
// handle event...
res.sendStatus(200);
});
Always verify webhook signatures in production to protect against spoofed requests.