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
| Failure | What happens |
|---|---|
| Your receiver acknowledged before it committed | The 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 answered | A CDN, load balancer, WAF, or gateway returns 200 for a request your code never handled, or absorbs it during a deploy. |
| Retries ran out quietly | Exhaustion is a status on a row nobody reads. |
| The webhook was disabled or removed | The platform turned off the destination after repeated failures and notified an inbox nobody watches. Deleting it can also delete its history. |
| A credential expired | An OAuth destination stops after a token is revoked. It looks like "no new rows", not an error. |
| Deduplication used the wrong key | Deduping on the record ID drops a legitimate second event about the same record, such as an edit. |
| The receiver crashed mid-processing | It 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.
| Demand | What it means |
|---|---|
| Signed payloads over the raw bytes | An 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 ID | Constant across retries of one delivery, different between two events about the same record. It's your idempotency key. |
| At-least-once delivery, stated plainly | Any system that retries will sometimes deliver twice. |
| A published retry schedule and timeout | How many attempts, at what intervals, and what counts as a failure. |
| An alert on exhaustion, sent to a person | A status field isn't an alert. |
| A delivery log | Attempt count, last status code, next retry, and the error, with a stated retention window. |
| Transport rules | HTTPS only, no redirect following, no private addresses. Following redirects turns a webhook into a request-forgery tool. |
| A safe manual retry | A way to re-send failed deliveries after you fix the receiver. |
Audit a tool with ten questions
- Is the payload signed over the raw body?
- Is there a delivery ID, stable across retries and distinct from the response ID?
- How many retries, at what intervals?
- Which status codes and timeout count as failure?
- What happens after the last attempt?
- Who receives the alert, and can it go to a shared inbox?
- Can I see per-delivery attempt history, and for how long?
- Can I re-send a failed delivery after fixing my endpoint?
- Does it follow redirects or allow private or plain-HTTP destinations?
- 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
- Verify the signature against the raw bytes. Reject on failure.
- Insert the delivery ID and payload into a table with a unique constraint on the ID.
- Return 2xx only after that insert commits. If storage fails, let the sender retry.
- 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
| Guarantee | Fillo |
|---|---|
| Signature | X-Fillo-Signature, a hex HMAC-SHA256 of the raw body |
| Idempotency key | X-Fillo-Delivery-Id, stable across retries of one delivery |
| Failure rule | Non-2xx status or a 10-second timeout |
| Retries | Up to 6 attempts with backoff from about 1 minute to 6 hours |
| Delivery | At least once |
| Transport | HTTPS only in production, no redirects |
| Visibility | Per-destination status and recent deliveries on the form's Activity page |
| Alerts | Email 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.
Related
- Support intake example: A live form whose responses flow through these delivery paths.
- Webhooks: Headers, payload shapes, signature verification, and the retry schedule.
- Delivery health and retry: Read destination status, inspect deliveries, and retry safely.
- Webhook signature failed: Fix a verification mismatch.