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
webhooks:manage
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
webhooks:manage
Scope: webhooks:manage
Body (application/json)
urlstring (uri)requiredeventsstring: 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[]requiredResponse 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
webhooks:manage
Scope: webhooks:manage
Parameters
webhookpath · stringrequiredWebhook 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
webhooks:manage
Scope: webhooks:manage
Parameters
webhookpath · stringrequiredWebhook 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 queuedmessage.sentMessage sentmessage.deliveredMessage deliveredmessage.failedMessage faileddevice.onlineDevice came onlinedevice.offlineDevice went offlinechat.messageChat: new messagechat.message.editedChat: message editedchat.message.deletedChat: message deletedchat.actionChat: group eventchat.reactionChat: reactions changedchat.operator_requestedChat: operator requestedlead.newLead: newlead.updatedLead: updatedscenario.doneScenario: donescenario.failedScenario: failedscenario.cancelledScenario: cancelledchannel.connectedChannel connectedchannel.disconnectedChannel disconnectedchannel.revokedChannel session revokedbot.callbackBot: button pressedbot.inline_queryBot: inline querybot.inline_chosenBot: inline result chosenbot.precheckoutBot: pre-checkout querybot.shippingBot: shipping querybot.join_requestBot: join requestbot.stoppedBot: user blocked/unblocked the botbot.reactionBot: reactionbot.business_messageBot: business messagebot.paid_mediaBot: paid media purchasedbot.paymentBot: invoice paidcall.incomingCall: incomingcall.endedCall: endedcall.groupCall: voice chat changedsecret.chatSecret chat: requested / accepted / discardedcontact.referralContact: new referralcontact.link_clickContact: link clickedcontact.updatedContact: tags/variables changedcomment.newInstagram: new commentorder.createdOrder: createdorder.paidOrder: paidorder.expiredOrder: subscription expiredorder.updatedOrder: delivery status changedbooking.createdBooking: createdbooking.confirmedBooking: confirmedbooking.cancelledBooking: cancelledPayload
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.