Skip to main content

Webhooks

Webhooks Overview​

Receive real-time notifications when events occur in Api.co.id.

When an event happens (like receiving a message), we'll send an HTTP POST request to your configured URL with the event data.

How It Works​

  1. Configure a webhook URL in your dashboard
  2. Select which events you want to receive
  3. We send POST requests to your URL when events occur
  4. Your server processes the event and responds with 200 OK

Manage webhooks →

Webhook Events​

Available webhook events you can subscribe to.

EventDescription
message.receivedNew incoming message
message.sentMessage sent successfully
message.deliveredMessage delivered to recipient
message.readMessage read by recipient
message.failedMessage delivery failed

Payload Format​

{
"event": "message.received",
"timestamp": "2025-01-07T10:30:00Z",
"data": {
"message_id": "msg_xyz789",
"customer_id": "clxxx1234567890",
"customer_phone": "628123456789",
"channel": "whatsapp",
"direction": "inbound",
"message_type": "text",
"content": "Hello, I need help!",
"media_url": null
}
}
info

For media messages (image/video/audio/document/sticker), the payload also includes a media_status field: "ok" (media_url is a usable re-hosted URL), "download_failed" (we could not re-host the media), or "unsupported" (Meta sent no downloadable media, e.g. view-once messages). Only trust media_url when media_status is "ok". The field is absent for non-media messages.

Webhook Security​

Verify webhook signatures to ensure requests are from Api.co.id.

All webhook requests include an HMAC-SHA256 signature in the X-Webhook-Signature header. Verify this signature in your server to ensure the request actually came from Api.co.id.

warning

Important: Always verify the webhook signature before processing events. Without verification, anyone could send fake webhook payloads to your server.

How Signature Works​

  1. When you create a webhook endpoint, we generate a unique webhook_secret
  2. For each event, we compute: HMAC-SHA256(payload, webhook_secret)
  3. The hex-encoded signature is sent in the X-Webhook-Signature header
  4. Your server recomputes the signature using the same secret and compares
  5. If they match, the request is authentic — process the event
  6. If they don't match, return 403 Forbidden

Where to Find Your Webhook Secret​

Go to Developers → Webhooks and click on your webhook endpoint. The secret is shown only once when you create the webhook — store it securely in your environment variables (e.g., WEBHOOK_SECRET).

Verification Examples​

const crypto = require('crypto');

function verifyWebhookSignature(rawBody, signature, secret) {
if (!signature) return false;

const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');

// Use timingSafeEqual to prevent timing attacks
try {
return crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(signature, 'hex')
);
} catch {
return false;
}
}

// IMPORTANT: use express.raw() to get the unparsed body
app.post(
'/webhook',
express.raw({ type: 'application/json' }),
(req, res) => {
const signature = req.headers['x-webhook-signature'];
const rawBody = req.body.toString('utf8');

if (!verifyWebhookSignature(rawBody, signature, process.env.WEBHOOK_SECRET)) {
return res.status(403).json({ error: 'Invalid signature' });
}

const event = JSON.parse(rawBody);
// Process the event based on event.event_type
console.log('Received event:', event.event_type);

// Always return 200 quickly to avoid retries
res.status(200).send('OK');
}
);

Common Pitfalls​

❌ Comparing parsed JSON instead of raw body

The signature is computed on the EXACT raw bytes we send. If you parse the body to JSON and re-stringify it, even tiny formatting differences (whitespace, key order) will break the signature. Always use the raw body.

❌ Using == or === to compare signatures

Use constant-time comparison (timingSafeEqual, hmac.compare_digest, hash_equals, hmac.Equal) to prevent timing attacks.

❌ Storing webhook secret in code

Always store the webhook secret in environment variables, never commit it to git.

❌ Returning slow responses

Return 200 OK within 5 seconds. For heavy processing, queue the event and process asynchronously — otherwise we'll retry the delivery.

Test Your Verification​

Once you've implemented signature verification:

  1. Go to Developers → Webhooks
  2. Click on your webhook endpoint
  3. Click the "Test Webhook" button
  4. We'll send a test event with a valid signature — your server should return 200 OK

List Webhooks​

List your webhook endpoints and their status. Use this to check whether a webhook was auto-disabled after repeated delivery failures.

GET /api/v1/public/webhooks

info

A webhook is automatically disabled after 10 consecutive delivery failures (one successful delivery resets the counter). When disabled, is_active is false and disabled_at / disable_reason are set. Re-enable it via the Enable Webhook endpoint.

Example Request​

curl -X GET "https://chat.api.co.id/api/v1/public/webhooks" \
-H "Authorization: Bearer YOUR_API_KEY"

Response​

{
"success": true,
"data": [
{
"id": "cmxxx1234567890",
"name": "n8n production",
"url": "https://example.com/webhook",
"events": ["message.received"],
"channels": ["whatsapp"],
"is_active": false,
"status": "disabled",
"failure_count": 10,
"last_failed_at": "2026-06-24T03:00:00.000Z",
"disabled_at": "2026-06-24T03:05:00.000Z",
"disable_reason": "Auto-disabled after 10 consecutive delivery failures",
"created_at": "2026-06-01T00:00:00.000Z",
"updated_at": "2026-06-24T03:05:00.000Z"
}
]
}

Get Webhook​

Get the status of a single webhook endpoint by ID.

GET /api/v1/public/webhooks/:id

URL Parameters​

ParameterTypeRequiredDescription
idstringyesWebhook endpoint ID (from List Webhooks).

Example Request​

curl -X GET "https://chat.api.co.id/api/v1/public/webhooks/WEBHOOK_ID" \
-H "Authorization: Bearer YOUR_API_KEY"

Enable Webhook​

Re-enable a webhook endpoint that was disabled (e.g. auto-disabled after repeated delivery failures). Also resets failure_count and clears disabled_at / disable_reason.

POST /api/v1/public/webhooks/:id/enable

URL Parameters​

ParameterTypeRequiredDescription
idstringyesWebhook endpoint ID to enable.

Example Request​

curl -X POST "https://chat.api.co.id/api/v1/public/webhooks/WEBHOOK_ID/enable" \
-H "Authorization: Bearer YOUR_API_KEY"

Response​

{
"success": true,
"data": {
"id": "cmxxx1234567890",
"name": "n8n production",
"url": "https://example.com/webhook",
"events": ["message.received"],
"channels": ["whatsapp"],
"is_active": true,
"status": "active",
"failure_count": 0,
"last_failed_at": "2026-06-24T03:00:00.000Z",
"disabled_at": null,
"disable_reason": null,
"created_at": "2026-06-01T00:00:00.000Z",
"updated_at": "2026-06-24T04:00:00.000Z"
},
"message": "Webhook endpoint enabled. Failure count has been reset."
}

Try it out​

tip

Press Try it and paste your API key (kc_live_...) into the API key (Bearer) field. Get the key from API Keys. Requests run against your live account, so endpoints that send, create, or delete act on real data.

  • GET/api/v1/public/webhooksList Webhooks
  • GET/api/v1/public/webhooks/{id}Get Webhook
  • POST/api/v1/public/webhooks/{id}/enableEnable Webhook
Was this page helpful?