API and webhooksUpdated 16 Oct 2026

Webhooks: delivery receipts, retries and signature checks

Receive delivery and reply events reliably, verify that they came from Telnxo and design for retries, duplicates and out-of-order delivery.

TD
Written by Telnxo Docs team
Reviewed by Telnxo Developer Relations
  • Published25 Sep 2026
  • Read time5 min
  • Length1,044 words
Share this guide

Why use webhooks

A webhook is an HTTP request Telnxo sends to your server when something happens: a message is delivered, a message fails, a customer replies or a call ends. Instead of asking us repeatedly whether a message arrived, you are told the moment it does. That is faster, cheaper and far more reliable at scale.

A webhook receiver looks simple, but it sits at the boundary of two systems and must cope with network failures, duplicates and hostile traffic. This guide explains how to build one that you can trust in production.

2xxstatus that means received
1event ID per event
HMACsignature to verify
3+retries on failure

The life of an event

  1. Something happens. A message changes state or a reply arrives
  2. We build an event. The event has an ID, a type, a timestamp and the related message data
  3. We sign it. We compute a signature over the raw body using your secret
  4. We call your URL. An HTTPS POST with the JSON body and the signature header
  5. You answer. A 2xx response tells us you have it. Anything else makes us retry

Build a reliable receiver

  1. Answer quickly. Return a 2xx status within a couple of seconds and do the real work afterwards, for example by putting the event on a queue
  2. Verify the signature. Compute an HMAC of the raw body with your secret and compare it in constant time to the header
  3. Be idempotent. The same event can arrive more than once. Store the event ID and ignore repeats
  4. Handle order. Events can arrive out of order. Use timestamps or a status ranking so that a late sent never overwrites delivered
  5. Log the raw body. For a short period, so that you can replay an event when something goes wrong
  6. Alert on failure. Watch your error rate and the age of your oldest unprocessed event

Verifying signatures

Illustrative. The header name and signing scheme below are an example. Use the exact values in your dashboard and API reference.
<?php
$raw       = file_get_contents('php://input');
$sent      = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
$expected  = hash_hmac('sha256', $raw, $secret);
if (!hash_equals($expected, $sent)) {
    http_response_code(401);
    exit;
}
http_response_code(200);   // acknowledge first
// then queue $raw for processing

Always sign and verify the raw body exactly as received. If you decode the JSON and encode it again, whitespace and key order can change, and the signature will no longer match.

Why a constant-time comparison

An ordinary string comparison stops at the first different character. An attacker who can time responses can use that to guess a signature one character at a time. A constant-time function such as hash_equals removes the leak.

Retries and what they mean for you

If your endpoint is unavailable or returns an error, we retry with increasing delays. This protects you from short outages, but it has consequences you should design for.

  • Duplicates. If your server processed an event but timed out before answering, we will send it again. Idempotency is mandatory.
  • Delay. An event can arrive minutes or hours late after an outage. Do not assume it is fresh.
  • Order. Retries mean later events may arrive before earlier ones.
  • Giving up. After the retry window we stop. Use the API to reconcile anything you missed.

Choosing what to subscribe to

EventTypical use
Message deliveredMark an order notification as received
Message failedRetry on another channel or alert support
Inbound replyOpen a conversation or process an opt-out
Call completedRecord duration for billing reconciliation

Subscribe only to what you use. Every extra event type is more traffic to process and more code to maintain.

Security checklist

  • Receive on HTTPS only, with a valid certificate
  • Verify every signature before touching the payload
  • Keep the secret in a secret manager and rotate it periodically
  • Reject requests with a timestamp far in the past, to limit replays
  • Do not expose the endpoint URL publicly or put it in client-side code
  • Limit what the endpoint can do: it should record and queue, not run arbitrary actions

Testing

Test in the sandbox first. Send a message, confirm the receipt arrives, then stop your server and confirm the retry. Finally replay the same event twice and check that your system records it once. Those three tests find most real-world webhook bugs.

A reference architecture

A dependable design has three small parts. The receiver is a thin HTTP endpoint that verifies the signature, writes the raw event to a durable queue or table and returns 200. The worker reads from the queue, checks the event ID against a table of processed events and applies the change to your business data. The reconciler runs on a schedule, asks the API for messages in an unresolved state older than a threshold and repairs any gaps.

Keeping these separate means a slow database never makes the receiver time out, a bug in the worker never loses events, because they sit safely in the queue, and a missed webhook does not leave your data wrong forever.

Table design for idempotency

Create a table with a unique key on the event ID. Before processing, insert the ID. If the insert fails because the key exists, the event was already handled and you simply skip it. This works across many servers without any locking.

Monitoring

SignalAlert when
Receiver error rateAbove one per cent for ten minutes
Queue ageOldest event older than five minutes
Signature failuresAny sudden increase, which may mean a rotated secret or an attack
Reconciliation fixesMore than a handful per day, which means webhooks are being lost

Review these alongside your delivery dashboards. A webhook problem often shows up first as customers reporting missing notifications, so link the alerts to an on-call owner.

Handling failures gracefully

Decide in advance what your system does when something unexpected arrives: an unknown event type, a missing field or a message ID you have never seen. The safe default is to store the raw event, return 200 and raise a low-priority alert. Rejecting unknown events makes us retry them pointlessly, while crashing on them loses the information you need to fix the problem.

Further reading

Frequently asked questions

Should I use the parsed JSON or the raw body for the signature?

Use the raw body exactly as received. Re-encoding JSON changes the bytes and breaks the signature.

How fast must my endpoint respond?

Respond within a few seconds. Acknowledge first, process afterwards.

What if I miss events?

Use the API to reconcile messages by ID and time range.

Can I have several webhook URLs?

Use one endpoint that routes internally, unless your dashboard offers per-event URLs.

What status codes cause a retry?

Any non-2xx response, a timeout or a connection failure.

Was this guide helpful?Your answer helps us improve our documentation.
Share this guideSend it to a teammate who needs it.

ابدأ الإرسال في دقائق

أنشئ حسابًا وتحقق من المرسل وأرسل أول رسالة اليوم.