Skip to main content

Why form webhooks fail silently

How form responses go missing while every system reports success, what delivery guarantees to demand from a form tool, and how to build a receiver.

Build this with your agent. Copy a prompt that hands this guide to Claude Code, Codex, Cursor, or any coding agent.

A form webhook can lose responses while the form, the sender, and your dashboard all report success. This page lists how that happens, the delivery guarantees to demand from any form tool, and a receiver pattern that closes the gap.

How a delivery goes missing without an error

FailureWhat happens
Your receiver acknowledged before it committedThe endpoint returns 200, then the database write or CRM call fails. The sender has already forgotten the event. This is the most common one.
Something in front of your app answeredA CDN, load balancer, WAF, or gateway returns 200 for a request your code never handled, or absorbs it during a deploy.
Retries ran out quietlyExhaustion is a status on a row nobody reads.
The webhook was disabled or removedThe platform turned off the destination after repeated failures and notified an inbox nobody watches. Deleting it can also delete its history.
A credential expiredAn OAuth destination stops after a token is revoked. It looks like "no new rows", not an error.
Deduplication used the wrong keyDeduping on the record ID drops a legitimate second event about the same record, such as an edit.
The receiver crashed mid-processingIt claimed the event and crashed before finishing. The sender saw an acknowledgment, so no retry is coming.

Most of these sit on the receiver's side. A sender with good delivery semantics can still lose data if your endpoint acknowledges too early.

What good delivery semantics look like

Demand these from any form tool.

DemandWhat it means
Signed payloads over the raw bytesAn HMAC over the exact request body, and docs that say to verify before parsing. Re-serializing changes the bytes.
A delivery ID separate from the record IDConstant across retries of one delivery, different between two events about the same record. It's your idempotency key.
At-least-once delivery, stated plainlyAny system that retries will sometimes deliver twice.
A published retry schedule and timeoutHow many attempts, at what intervals, and what counts as a failure.
An alert on exhaustion, sent to a personA status field isn't an alert.
A delivery logAttempt count, last status code, next retry, and the error, with a stated retention window.
Transport rulesHTTPS only, no redirect following, no private addresses. Following redirects turns a webhook into a request-forgery tool.
A safe manual retryA way to re-send failed deliveries after you fix the receiver.

Audit a tool with ten questions

  1. Is the payload signed over the raw body?
  2. Is there a delivery ID, stable across retries and distinct from the response ID?
  3. How many retries, at what intervals?
  4. Which status codes and timeout count as failure?
  5. What happens after the last attempt?
  6. Who receives the alert, and can it go to a shared inbox?
  7. Can I see per-delivery attempt history, and for how long?
  8. Can I re-send a failed delivery after fixing my endpoint?
  9. Does it follow redirects or allow private or plain-HTTP destinations?
  10. Does delivery history survive deleting a destination?

Then test what docs can't answer. Point the webhook at an endpoint that returns 500, submit a response, and watch the tool for a few hours.

Build a receiver for at-least-once delivery

  1. Verify the signature against the raw bytes. Reject on failure.
  2. Insert the delivery ID and payload into a table with a unique constraint on the ID.
  3. Return 2xx only after that insert commits. If storage fails, let the sender retry.
  4. Process the inbox in a separate worker with its own retries.

Never claim a delivery ID and then start uncommitted work. A crash between those steps loses the retry. A simple handler can write the delivery ID and its database change in one transaction instead.

Monitor the inbox, not the endpoint. "No deliveries in the last hour on a form that usually gets ten" catches the rest.

What Fillo does

GuaranteeFillo
SignatureX-Fillo-Signature, a hex HMAC-SHA256 of the raw body
Idempotency keyX-Fillo-Delivery-Id, stable across retries of one delivery
Failure ruleNon-2xx status or a 10-second timeout
RetriesUp to 6 attempts with backoff from about 1 minute to 6 hours
DeliveryAt least once
TransportHTTPS only in production, no redirects
VisibilityPer-destination status and recent deliveries on the form's Activity page
AlertsEmail when retries are exhausted or a connection needs reauthorizing

The webhooks reference has the headers, payloads, and verification code. Delivery health covers status, alerts, retention, and safe retries.

This page for agents: /guides/silent-webhook-failures.md · index at /llms.txt

Updated

Was this page helpful?