Back
security webhooks engineering
APR 14

Secure Form Webhooks Need Signatures, Retries, and Failure Modes

What makes a form webhook production-grade: HMAC signatures, replay protection, retries, auto-disable behavior, and clear payloads.

👤
Postbox Team
· · 5 min read

A webhook looks simple until it matters.

You receive a submission. You POST JSON to a URL. The receiving system does something useful. Easy.

Then production arrives.

Was the request really from your form backend? Did someone replay an old payload? What happens if the receiver times out? How many times should delivery retry? What if the endpoint has been broken for three days? What if the same submission is delivered twice? What if the payload gets logged somewhere it should not?

A webhook is not just an HTTP request. It is a trust boundary.

If form submissions drive workflows — leads, support tickets, invoices, onboarding, incident reports — then webhook delivery needs the same care as any other integration surface.

The first question: authenticity

When your backend receives a webhook, it needs to know who sent it.

IP allowlists are brittle. Shared secrets in URLs leak. Basic auth is often copied into logs. The common pattern that holds up best is request signing.

Postbox webhook destinations use HMAC-SHA256 signatures. Each webhook destination gets a secret when it is created. Every delivery includes signing headers:

  • webhook-id
  • webhook-timestamp
  • webhook-signature

The receiver computes:

HMAC-SHA256(secret, "{webhook-id}.{webhook-timestamp}.{body}")

Then it compares the result with the signature using a timing-safe comparison.

This gives the receiver a strong answer to the most important question: did someone with the shared secret sign this exact body at this time?

Replay protection matters

A valid signature alone is not enough.

If an attacker obtains one valid request, they might try to replay it later. That is why the timestamp is part of the signed content. The receiver should reject old timestamps, commonly anything older than five minutes.

This turns the signature into a statement about both content and freshness.

The webhook body, event ID, and timestamp all travel together. Change any part and the signature fails. Replay too late and the receiver rejects it.

Security often comes down to these small, boring details. They are easy to skip when building a one-off webhook integration. They are exactly the details infrastructure should handle consistently.

Payloads should carry context, not mystery

A webhook payload should be useful on arrival.

A bad payload says: “submission created” and forces the receiver to call back into the API for everything else. That creates latency, failure coupling, and more authentication surface.

A good payload includes the information needed to act:

  • form identity;
  • submission identity;
  • submitted data;
  • spam status and reason;
  • translation if available;
  • smart reply status if enabled;
  • timestamps and processing metadata.

Postbox sends the processed result, not just the raw record. That matters because the receiver usually wants to make a decision. Should this lead enter the CRM? Should this message open a support ticket? Should this spam submission be ignored? Should this translated text go to the international team?

A webhook should reduce the work downstream, not move the ambiguity somewhere else.

Retries need restraint

Networks fail. Receivers deploy. DNS changes. Queues back up. A single failed delivery should not mean permanent data loss.

So webhooks need retries.

But retries without restraint become accidental denial-of-service. If the receiving endpoint is down and you hammer it endlessly, you have converted resilience into pressure. The right approach is bounded retries with backoff and jitter.

Backoff gives the receiver time to recover. Jitter prevents synchronized retry storms. Bounds prevent a broken destination from consuming resources forever.

Postbox retries failed webhook deliveries automatically. If a destination keeps failing, it is eventually paused and the owner is notified.

That last step is important.

Failure should become visible

Silent failure is worse than loud failure.

If a webhook destination is broken, the owner needs to know. If the endpoint returns errors repeatedly, the system should stop pretending delivery will fix itself. Auto-disable behavior protects both sides: Postbox stops sending to a known-broken destination, and the owner gets a clear signal to repair it.

This is one of those operational details people rarely specify in early versions of a webhook system. Then a customer discovers that submissions were failing for days.

Production-grade delivery is not just “retry.” It is retry, observe, pause, and notify.

Secrets should be treated as secrets

Webhook signing secrets are shown once when a destination is created. Store them in a secrets manager or environment variable. Do not commit them. Do not paste them into dashboards that too many people can read. Rotate them if exposed.

Postbox supports regenerating a webhook secret. The old secret is invalidated immediately. That makes rotation possible when teams change, logs leak, or deployment environments are rebuilt.

Key rotation is not glamorous. Neither is insurance. Both matter only when you suddenly need them.

Webhooks are part of the contract

It is tempting to think of webhooks as a convenience layer attached to forms. We think of them as part of the post-submit contract.

The submitter provides data. Postbox validates and processes it. Destinations receive the result. If the destination is your backend, then the webhook is where form data enters your system of record.

That boundary deserves signatures. It deserves replay protection. It deserves clear payloads, retries, and explicit failure behavior.

Otherwise, every form becomes a tiny integration platform built without integration-platform discipline.

The rule of thumb

If a webhook can trigger real work, it must be verifiable.

If it can fail, it must retry carefully.

If it keeps failing, it must become visible.

That is the difference between sending JSON and operating an integration surface.

Postbox handles the boring parts because the boring parts are where production systems usually break.


For the product overview, see Destinations on the features page. For broader trust posture, read the Postbox security audit.

Have thoughts?
Or connect for more dispatches.