Skip to content
ChatVendor ChatVendor

Webhooks

Get told when something happens instead of polling.

Register an endpoint under Developer → Webhooks (or POST /api/v1/webhooks), choose events, and verify the X-ChatVendor-Signature header on every delivery. Failed deliveries are retried with backoff.

Webhooks

Get told when something happens: events, payload, signature, retries.

Base URL: https://chatvendor.net/api/v1 · every response is {"success": true, "data": …}

GET /webhooks List webhook endpoints

Scope: webhooks:manage

Response 200 · Webhook[]

id stringurl stringevents string: message.queued | message.sent | message.delivered | message.failed | device.online | device.offline | chat.message | chat.message.edited | chat.message.deleted | chat.action | chat.reaction | chat.operator_requested | lead.new | lead.updated | scenario.done | scenario.failed | scenario.cancelled | channel.connected | channel.disconnected | channel.revoked | bot.callback | bot.inline_query | bot.inline_chosen | bot.precheckout | bot.shipping | bot.join_request | bot.stopped | bot.reaction | bot.business_message | bot.paid_media | bot.payment | call.incoming | call.ended | call.group | secret.chat | contact.referral | contact.link_click | contact.updated | comment.new | order.created | order.paid | order.expired | order.updated | booking.created | booking.confirmed | booking.cancelled[]enabled boolean

curl https://chatvendor.net/api/v1/webhooks \
  -H "Authorization: Bearer cv_live_xxxxxxxxxxxx"
POST /webhooks Create a webhook endpoint

Scope: webhooks:manage

Body (application/json)

urlstring (uri)required
eventsstring: message.queued | message.sent | message.delivered | message.failed | device.online | device.offline | chat.message | chat.message.edited | chat.message.deleted | chat.action | chat.reaction | chat.operator_requested | lead.new | lead.updated | scenario.done | scenario.failed | scenario.cancelled | channel.connected | channel.disconnected | channel.revoked | bot.callback | bot.inline_query | bot.inline_chosen | bot.precheckout | bot.shipping | bot.join_request | bot.stopped | bot.reaction | bot.business_message | bot.paid_media | bot.payment | call.incoming | call.ended | call.group | secret.chat | contact.referral | contact.link_click | contact.updated | comment.new | order.created | order.paid | order.expired | order.updated | booking.created | booking.confirmed | booking.cancelled[]required

Response 201 · Webhook

id stringurl stringevents string: message.queued | message.sent | message.delivered | message.failed | device.online | device.offline | chat.message | chat.message.edited | chat.message.deleted | chat.action | chat.reaction | chat.operator_requested | lead.new | lead.updated | scenario.done | scenario.failed | scenario.cancelled | channel.connected | channel.disconnected | channel.revoked | bot.callback | bot.inline_query | bot.inline_chosen | bot.precheckout | bot.shipping | bot.join_request | bot.stopped | bot.reaction | bot.business_message | bot.paid_media | bot.payment | call.incoming | call.ended | call.group | secret.chat | contact.referral | contact.link_click | contact.updated | comment.new | order.created | order.paid | order.expired | order.updated | booking.created | booking.confirmed | booking.cancelled[]enabled boolean

curl -X POST https://chatvendor.net/api/v1/webhooks \
  -H "Authorization: Bearer cv_live_xxxxxxxxxxxx"
  -H "Content-Type: application/json" \
  -d '{"url":"…","events":[]}'
PATCH /webhooks/{webhook} Update a webhook endpoint

Scope: webhooks:manage

Parameters

webhookpath · stringrequired

Webhook public id

Response 200 · Webhook

id stringurl stringevents string: message.queued | message.sent | message.delivered | message.failed | device.online | device.offline | chat.message | chat.message.edited | chat.message.deleted | chat.action | chat.reaction | chat.operator_requested | lead.new | lead.updated | scenario.done | scenario.failed | scenario.cancelled | channel.connected | channel.disconnected | channel.revoked | bot.callback | bot.inline_query | bot.inline_chosen | bot.precheckout | bot.shipping | bot.join_request | bot.stopped | bot.reaction | bot.business_message | bot.paid_media | bot.payment | call.incoming | call.ended | call.group | secret.chat | contact.referral | contact.link_click | contact.updated | comment.new | order.created | order.paid | order.expired | order.updated | booking.created | booking.confirmed | booking.cancelled[]enabled boolean

curl -X PATCH https://chatvendor.net/api/v1/webhooks/{webhook} \
  -H "Authorization: Bearer cv_live_xxxxxxxxxxxx"
DELETE /webhooks/{webhook} Delete a webhook endpoint

Scope: webhooks:manage

Parameters

webhookpath · stringrequired

Webhook public id

Response 204

Deleted

curl -X DELETE https://chatvendor.net/api/v1/webhooks/{webhook} \
  -H "Authorization: Bearer cv_live_xxxxxxxxxxxx"

Get told when something happens instead of polling for it. Add an endpoint under Developer → Webhooks, or through the API above with a webhooks:manage key. Every endpoint gets a secret; every delivery is signed.

Events

Messenger events: chat.message, chat.message.edited, chat.message.deleted, chat.action, chat.operator_requested, lead.new, lead.updated, scenario.done|failed|cancelled, channel.connected|disconnected|revoked. The payload carries channel_id and the event data.

message.queuedMessage queued
message.sentMessage sent
message.deliveredMessage delivered
message.failedMessage failed
device.onlineDevice came online
device.offlineDevice went offline
chat.messageChat: new message
chat.message.editedChat: message edited
chat.message.deletedChat: message deleted
chat.actionChat: group event
chat.reactionChat: reactions changed
chat.operator_requestedChat: operator requested
lead.newLead: new
lead.updatedLead: updated
scenario.doneScenario: done
scenario.failedScenario: failed
scenario.cancelledScenario: cancelled
channel.connectedChannel connected
channel.disconnectedChannel disconnected
channel.revokedChannel session revoked
bot.callbackBot: button pressed
bot.inline_queryBot: inline query
bot.inline_chosenBot: inline result chosen
bot.precheckoutBot: pre-checkout query
bot.shippingBot: shipping query
bot.join_requestBot: join request
bot.stoppedBot: user blocked/unblocked the bot
bot.reactionBot: reaction
bot.business_messageBot: business message
bot.paid_mediaBot: paid media purchased
bot.paymentBot: invoice paid
call.incomingCall: incoming
call.endedCall: ended
call.groupCall: voice chat changed
secret.chatSecret chat: requested / accepted / discarded
contact.referralContact: new referral
contact.link_clickContact: link clicked
contact.updatedContact: tags/variables changed
comment.newInstagram: new comment
order.createdOrder: created
order.paidOrder: paid
order.expiredOrder: subscription expired
order.updatedOrder: delivery status changed
booking.createdBooking: created
booking.confirmedBooking: confirmed
booking.cancelledBooking: cancelled

Payload

POST https://example.com/webhooks/sms
Content-Type: application/json
X-SMS-Event: message.delivered
X-SMS-Event-Id: evt_01k…
X-SMS-Delivery-Attempt: 1
X-SMS-Signature: t=1754825400,v1=8f3a…

{
  "event_id": "evt_01k…",
  "event": "message.delivered",
  "timestamp": "2026-08-11T09:30:00+00:00",
  "data": {
    "id": "msg_01k…",
    "status": "delivered",
    "to": "+998901234567",
    "segments": 1,
    "client_reference": "order_4389",
    "delivered_at": "2026-08-11T09:29:58+00:00"
  }
}

Verifying the signature

Compute HMAC SHA-256 over "{timestamp}.{raw body}" with your endpoint's secret. Sign the raw body — re-encoding the JSON first will change the bytes and the signature will not match. The timestamp is inside the signed string, which is what stops a captured payload being replayed later.

// PHP
$raw = file_get_contents('php://input');
$header = $_SERVER['HTTP_X_SMS_SIGNATURE'] ?? '';

parse_str(strtr($header, ',', '&'), $parts);

if (abs(time() - (int) $parts['t']) > 300) {
    http_response_code(400);   // too old, treat as replay
    exit;
}

$expected = hash_hmac('sha256', $parts['t'] . '.' . $raw, $secret);

if (! hash_equals($expected, $parts['v1'])) {
    http_response_code(401);
    exit;
}
// Node.js (express, raw body required)
app.post('/webhooks/sms', express.raw({ type: 'application/json' }), (req, res) => {
  const parts = Object.fromEntries(
    req.get('X-SMS-Signature').split(',').map((p) => p.split('=')),
  );

  const expected = crypto
    .createHmac('sha256', process.env.WEBHOOK_SECRET)
    .update(`${parts.t}.${req.body}`)
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))) {
    return res.sendStatus(401);
  }

  res.sendStatus(200);   // acknowledge fast, process afterwards
});

Retries

Any 2xx counts as delivered. Anything else is retried 1 min, 5 min, 30 min, 2 h after the first attempt. An endpoint that fails 20 times in a row is disabled automatically — re-enable it once it is fixed.

Every event carries a unique event_id. A retry reuses it, so store the ones you have processed and ignore repeats — a delivery that timed out on your side may still have been received.

Reply quickly. We wait 10 seconds; do the real work after responding, not before.

Per-message callbacks

Passing callback_url on a send delivers that message's events to that URL as well. Those are not signed — there is no shared secret behind a one-off URL — so treat them as a hint and confirm with GET /api/v1/messages/{id} before acting on anything that matters.