Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.sahut.id/llms.txt

Use this file to discover all available pages before exploring further.

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

  1. You create a webhook endpoint on your server (a URL that accepts POST requests)
  2. You register that URL in Sahut
  3. When an event occurs, Sahut sends a POST request with a JSON payload to your URL
  4. 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

  1. Go to Settings → Integrations → Webhooks
  2. Click Tambah Webhook (Add Webhook)
  3. Enter your Endpoint URL
  4. Select the Events you want to receive
  5. Click Simpan (Save)

Via the API

POST /webhooks

Request body

url
string
required
The HTTPS URL to receive webhook events.
events
array
required
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

DELETE /webhooks/{id}
id
string
required
Webhook ID to delete.

Event types

EventTriggered when
conversation.createdA new conversation is created
conversation.updatedA conversation’s status, assignee, or labels change
conversation.resolvedA conversation is marked as resolved
message.createdA new message is sent or received in any conversation
contact.createdA new contact profile is created
contact.updatedA contact’s details are updated

Webhook payload format

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.