Webhooks
Signed HTTP callbacks delivered to your registered endpoint when OTP events happen.
Events
| Event | Fires when | Status |
|---|---|---|
otp.created | An OTP is generated by /v1/otp/send | Active |
otp.verified | A code is successfully verified | Active |
otp.failed | An incorrect code is submitted, or max attempts are exceeded | Active |
otp.expired | A verification is attempted after the 5-minute TTL | Active |
identity.created | — | Reserved, not yet emitted |
identity.updated | — | Reserved, not yet emitted |
application.created | — | Reserved, not yet emitted |
application.deleted | — | Reserved, not yet emitted |
You can register a webhook for a reserved event today — it just won't receive any deliveries until that event type is wired up on the platform side. Don't build logic that depends on them yet.
Delivery payload
{
"id": "e3a9c1f0-...",
"event": "otp.verified",
"createdAt": "2026-07-03T14:22:10.000Z",
"payload": {
"requestId": "a4b8c2d1...",
"phone": "+999 482 918 102",
"verifiedAt": "2026-07-03T14:22:10.000Z"
}
}
The OTP code itself is never included in any webhook payload, under any event type.
Delivery headers
| Header | Meaning |
|---|---|
x-cobinar-signature | sha256=<hex digest> — HMAC-SHA256 of the raw request body using your application's Webhook Secret |
x-cobinar-event | The event type, e.g. otp.verified |
x-cobinar-event-id | Unique ID for this delivery — use it to deduplicate retried deliveries |
x-cobinar-timestamp | ISO 8601 timestamp the event was created |
Verifying the signature
const crypto = require('crypto');
function verifyWebhook(rawBody, signatureHeader, webhookSecret) {
const expected = crypto
.createHmac('sha256', webhookSecret)
.update(rawBody) // the exact raw bytes, before any JSON.parse
.digest('hex');
return `sha256=${expected}` === signatureHeader;
}
app.post('/webhooks/auth', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-cobinar-signature'];
if (!verifyWebhook(req.body, sig, process.env.COBINAR_WEBHOOK_SECRET)) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body);
// handle event.type / event.payload
res.sendStatus(200);
});
Retries
Delivery is handled by a dedicated worker so a slow endpoint never delays your API response. Each delivery gets up to 3 attempts with backoff (1s, then 4s between attempts) and an 8-second timeout per attempt. The final outcome is recorded against the webhook and visible in the console's Webhooks page.
Testing without a server
Use the webhook-echo tool in the API Sandbox to see a real signed delivery — and verify it in your browser — before you've written any receiving code. See Testing Your Integration.