Webhooks guide

Receive delivery events at your own URL with HMAC-SHA256 verification.

View as Markdown

When a message reaches a terminal state (delivered, failed, billed, or bounced) Teekrr POSTs a JSON event to every active webhook endpoint you’ve registered for that event type. Webhooks are managed in-app at /api-management → Webhooks.

Headers Teekrr sends

HeaderValue
Content-Typeapplication/json
X-Teekrr-SignatureHMAC-SHA256 hex of the raw request body, signed with your webhook’s secret hash
X-Teekrr-EventThe event name: delivered | failed | billed | bounced

Payload envelope

1{
2 "event": "delivered",
3 "timestamp": "2026-05-06T10:15:00.123Z",
4 "data": { "...event-specific fields..." }
5}

The exact data shape depends on the event — see the API Reference section’s “Webhooks” group for per-event schemas and examples.

Verifying the signature

The signature is HMAC-SHA256(secretHash, rawBody). The HMAC key is the SHA-256 hash of your plaintext secret, not the plaintext itself. You receive the plaintext once at creation; Teekrr stores only the hash.

1import express from "express";
2import { createHash, createHmac, timingSafeEqual } from "crypto";
3
4const app = express();
5
6// IMPORTANT: capture raw body. JSON parsing changes whitespace and breaks the signature.
7app.post(
8 "/webhooks/teekrr",
9 express.raw({ type: "application/json" }),
10 (req, res) => {
11 const secret = process.env.TEEKRR_WEBHOOK_SECRET;
12 const signature = req.header("X-Teekrr-Signature");
13 const event = req.header("X-Teekrr-Event");
14
15 const secretHash = createHash("sha256").update(secret).digest("hex");
16 const expected = createHmac("sha256", secretHash)
17 .update(req.body) // Buffer of raw bytes
18 .digest("hex");
19
20 const a = Buffer.from(expected, "hex");
21 const b = Buffer.from(signature ?? "", "hex");
22 if (a.length !== b.length || !timingSafeEqual(a, b)) {
23 return res.status(401).send("invalid signature");
24 }
25
26 const payload = JSON.parse(req.body.toString("utf8"));
27 // payload.event === event (e.g. "delivered")
28 // payload.data === { messageUuid, ... }
29
30 // ... your business logic ...
31
32 res.status(200).send("ok");
33 }
34);

Always use the raw, untouched request body for verification. JSON parsing changes whitespace and breaks the signature. Express needs express.raw({ type: "application/json" }). FastAPI needs await request.body() before request.json().

Retries & timeouts

  • Teekrr applies a 10-second connect/read timeout per delivery.
  • Teekrr does not automatically retry failed webhook deliveries today — design your endpoint to be idempotent, and use the dashboard’s Message Logs as a backstop.
  • Every delivery attempt is recorded at /api-management → Webhooks → Delivery Logs.

Idempotency

Each event includes the message UUID inside data (e.g. data.messageUuid). De-dupe on (event, messageUuid) to be safe against future retry-on-failure rollouts.

The four event types

EventWhenChannel
deliveredProvider confirms delivery to recipient device / inboxSMS · WhatsApp · Email
failedProvider returns permanent failure; reserved credit releasedSMS · WhatsApp · Email
billedReserved credit settled (provider accepted message)SMS · WhatsApp · Email
bouncedSES bounce notification (hard or soft)Email only

For full per-event payload shapes, see the Webhooks group in the API Reference.