> ## Documentation Index
> Fetch the complete documentation index at: https://docs.yotel.in/llms.txt
> Use this file to discover all available pages before exploring further.

# Troubleshooting webhooks

> Common delivery failures and how to diagnose them.

## Deliveries fail with HTTP 401 on my side

**95% of the time: signature mismatch from framework body re-parse.**

Your framework (Express, FastAPI, Rails, etc.) is probably parsing
the JSON body before your handler sees it, then re-serializing
when you compute the HMAC. Re-serialization drifts whitespace or
key order → signature mismatch.

**Fix:** verify against the **raw bytes**. In Express, use
`express.raw({ type: "application/json" })`. In FastAPI, use
`await request.body()` before any JSON parsing. See
[signature verification](/webhooks/signature-verification) for code.

**Second most common: clock skew.** Your server's clock is off by
more than 5 minutes. Run `timedatectl status` (Linux) or check
Windows Time service. Sync with NTP.

## Every delivery fails with 404

Your webhook URL is wrong. Check the dashboard → Webhooks → the
subscription → URL field. A typo in the path or a missing route in
your app returns 404 on our side, which we dead-letter without retry
(see [retries](/webhooks/retries)).

## Deliveries were working, now they're all failing

<Steps>
  <Step title="Check the delivery log">
    Dashboard → Webhooks → Delivery log. Click a failed row to see the
    HTTP status and response snippet. Often your handler logs the
    error body there.
  </Step>

  <Step title="Check if auto-disable fired">
    If the subscription shows `disabled_at` set, you crossed the
    consecutive-failures threshold. Re-enable from the dashboard
    after fixing the root cause.
  </Step>

  <Step title="Check your endpoint's TLS certificate">
    We do proper certificate validation. An expired or self-signed
    cert will fail with a TLS handshake error (visible as a
    connection error in the delivery log).
  </Step>
</Steps>

## I got duplicate events

Expected. Retries reuse the same `event_id`. Also:

* **At-least-once delivery** — we don't guarantee exactly-once. If
  your 200 response was lost in the network, we'll retry.
* **Parallel subscriptions** — each subscription receives its own
  delivery. Two subs to the same URL will POST twice for the same event.

**Fix:** key on `event_id` in your handler. Store processed IDs in
Redis or DB with a TTL >= 7.5 hours (our total retry window).

## I'm missing events I should have received

<AccordionGroup>
  <Accordion title="Is the subscription subscribed to that event type?">
    Dashboard → Webhooks → subscription → Event types. The full
    list is in the [event catalog](/webhooks/event-catalog).
  </Accordion>

  <Accordion title="Is the subscription disabled?">
    Look for `disabled_at` or `is_active: false`. Re-enable if appropriate.
  </Accordion>

  <Accordion title="Did the event fire at all?">
    Some events have preconditions — e.g. `call.ended` only fires on
    calls that reached `CHANNEL_HANGUP`. A call that never answered
    fires `call.started` and nothing else.
  </Accordion>

  <Accordion title="Is the delivery still pending?">
    Delivery log will show `status: pending` or `status: failed` with a
    `next_retry_at`. The retry sweep fires every 30s.
  </Accordion>

  <Accordion title="Did auto-disable kick in since your last successful delivery?">
    See above.
  </Accordion>
</AccordionGroup>

## Force a test delivery

Dashboard → Webhooks → your subscription → **Test fire**. We POST a
synthetic event (event\_id prefixed with `test_`) to your endpoint
and show you the outcome right in the dashboard — HTTP status,
response body, duration. Lets you debug without waiting for real
events.

## My signing\_secret is leaked or lost

**Lost:** we can't recover it — only a hash lives on our side. Delete
and recreate the subscription.

**Leaked:** delete the subscription immediately. Recreate with a new
URL if you can (so attackers who captured the old URL also need to
rediscover the new one). Signing-secret rotation without downtime is
a v1.1 feature.

## Still stuck?

Email `support@yotel.in` with:

* The `X-Yotel-Delivery` header value from a failing delivery
* Your subscription ID (from the dashboard URL)
* Timestamp range of the failures

We'll look at the delivery log on our side and reply with what we see.
