website webhooks setup
One Event, Two Deliveries: Website Webhook Setup for Developers
Set up website webhooks with an HTTPS endpoint, raw body signature checks, idempotency, and tests using public inspectors and local tunnels before launch.

A webhook is an HTTP POST request that one app sends to a public HTTPS URL on your website when an event happens. To set up a webhook on your website, create an HTTPS POST endpoint, register its URL with the sender, and verify the HMAC signature on the raw request body before you process anything. Before going live, send test events to an inspector like Webhook.site to see exactly what the sender delivers.
TL;DR:
- Webhooks may arrive more than once and out of order, so store unique event IDs and define precedence rules before applying changes.
- Preserve exact request bytes for HMAC verification, compare signatures with a constant time function, and reject stale timestamps to block tampering and replay.
- Save verified events and enqueue durable jobs before returning 2xx; aim to acknowledge within 1 to 3 seconds so slow work does not trigger retries.
- Test with captured payloads and a local tunnel, then replay valid, altered, expired, and duplicate events; inspectors alone cannot validate handler behavior.
- Sender retry policies vary: 2xx stops delivery attempts, while timeouts and server errors commonly trigger retries; keep failed jobs for alerts and replay.
Table of Contents
- How a webhook delivery works from sender to endpoint
- Steps to set up a webhook on your website
- Webhook security: signatures, freshness and secrets
- Testing and debugging webhooks before they go live
- Retries, backoff and recovering missed events
- Deploying and monitoring webhook endpoints in production
- Code pattern: raw body, verify, dedupe, enqueue
- Our view on common webhook pitfalls
- Less manual integration wiring with WebsitePublisher
- FAQ
- Sources
How a webhook delivery works from sender to endpoint
Polling means your app asks “anything new?” on a timer. A webhook flips that: the sender calls you the moment something happens, such as a payment, a form submission or a new order. You get the data in near real time and skip the wasted requests.
Every delivery has the same parts, and your receiver has to handle each one:
- Sender: the service that fires the event and POSTs JSON to the URL you registered.
- Endpoint: a public HTTPS URL on your website or app that accepts POST requests with
application/json. - Payload: the JSON body describing the event, usually with a stable event ID and a type such as
invoice.paid. - Headers: most senders add a signature header and a timestamp, and some add a delivery or idempotency ID. Exact header names differ by provider, so read the sender’s docs before you code against them.
- Raw bytes: the signature is computed over the exact bytes the sender transmitted, so you must keep them untouched until you have verified it.
Delivery is at-least-once, and ordering is not guaranteed. The same event can arrive twice, and an “updated” event can land before the “created” one. Receiving webhooks without getting burned lists the receiver duties that follow from this: verify the signature on the raw body, reject replays through timestamp checks, deduplicate by event ID with a unique database constraint, and handle out-of-order arrivals by giving state a precedence rule.
Think of the sender as a courier who rings your doorbell. If nobody answers quickly, the courier comes back later. If you sign for the same parcel twice, you now own two parcels. Your job is to answer fast, check the label, and record what you signed for.
Some providers add a handshake on top. Discord, for example, requires you to acknowledge PING events and validate security headers, expects a fast acknowledgement, and will remove endpoints that fail validation. Check whether your sender has a similar verification step before you go live.
Steps to set up a webhook on your website
Follow these steps in order. Each one closes a gap that causes failures later.
- Create the endpoint. Add a route such as
POST /webhooks/provider-nameon your site. Serve it over HTTPS with a current TLS certificate. Accept only the POST method and only the expected content type, and return an error for everything else. - Capture the raw body first. Read the request bytes before any JSON parsing. Parsing and re-serializing changes whitespace and key order, which breaks the signature check.
- Verify the signature. Compute the HMAC over the raw body with your signing secret and compare it to the header value using a timing-safe function. Reject the request if it does not match.
- Register the URL and pick events. In the sender’s dashboard, paste your endpoint URL and subscribe only to the events you actually handle. Copy the signing secret into your secrets manager, not into your source code.
- Persist, enqueue, acknowledge. Write the raw payload and event ID to storage, put a job on a durable queue, and return a 2xx response right away. Do the real work from the queue.
- Set limits. Cap the maximum body size, apply rate limits, and validate the JSON against a schema after the signature passes.
Speed matters in step 5. The UR developer docs on webhooks advise responding as fast as possible, ideally within 1 to 3 seconds, with a 2xx status. They also advise acknowledging immediately, queuing the event for background processing, and keeping complex business logic out of the request cycle. A slow handler looks like a failure to the sender, which triggers a retry, which creates a duplicate.
The order inside the request also matters. Probo’s webhook delivery documentation describes a robust receiver that preserves the raw body, parses JSON only after verification, and atomically records a unique event ID before producing any side effects. That sequence means a retry that arrives mid-processing hits the unique constraint instead of running the side effect twice.
Pro Tip: Return a 2xx for events you do not handle yet, after verifying the signature. A 4xx on an unknown event type can make some senders mark your endpoint as broken.
If your webhook feeds lead data into a site, our guide to wiring email capture forms shows where those inbound events usually land next.
Webhook security: signatures, freshness and secrets
Your endpoint is a public URL. Anyone can POST to it, so every request is hostile until proven otherwise. These are the controls we treat as non-negotiable:
- Verify the HMAC signature on the raw body. Reject anything that fails, with no partial processing.
- Use a constant-time comparison. Plain string equality can leak timing information. In Node.js use
crypto.timingSafeEqual, and in Python usehmac.compare_digest. - Check freshness. If the sender signs a timestamp, reject deliveries outside a tolerance window. Choose a tolerance window based on your provider’s guidance.
- Record event IDs. A valid signature on an old request is still a replay. Store seen IDs and drop repeats.
- Protect secrets. Keep signing secrets in a secrets manager, give each webhook its own secret, and plan rotation with an overlap period where the provider supports it.
- Enforce transport rules. Require HTTPS with TLS 1.2 or newer, restrict allowed methods, and validate payload schemas.
- Add IP allowlisting only as a second layer. Sender IP ranges change, so never make them your only defense.
The reason for the constant-time rule is concrete. As How HTTP Works explains, normal string equality leaks timing information that an attacker can exploit to recover a signature byte by byte.
Constant-time comparison is required for HMAC checks, because normal string equality lets an attacker recover a signature byte by byte through timing. One line of code separates a safe verifier from an exploitable one.
For a broader checklist, Krosyn’s webhook security rules cover HMAC verification, HTTPS only, replay rejection, secret storage and rotation, schema validation, and body size and rate limits.
Testing and debugging webhooks before they go live
You should never debug a webhook for the first time in production. Use this workflow instead:
- Capture real requests with a public inspector. A request inspector such as Webhook.site gives you a temporary callback URL, so you can register it with the sender and see the headers, body and status codes it delivers.
- Tunnel your local server. Run your handler on your laptop and expose it with ngrok or cloudflared. The sender then delivers real events to code you can step through in a debugger.
- Save raw payloads and replay them. Store a captured body and its signature header as test fixtures. Unit-test your verifier against a valid signature, a tampered body and an expired timestamp.
- Test dedupe separately. Send the same event ID twice and assert that the side effect runs once.
- Use the provider’s redeliver feature. Most dashboards let you resend a past event, which reproduces a production delivery without waiting for real traffic.
Inspectors show you what the sender sends. They do not prove your handler is correct, so the unit tests and the replay step are what give you confidence.
Retries, backoff and recovering missed events
Senders decide what to do from your HTTP status code. The common rules, as Webhooker’s retry documentation describes them, are:
- 2xx is success and stops retries.
- 408, 429 and 5xx are retryable.
- Other 4xx responses are treated as permanent failures.
- Backoff is usually exponential with jitter and a cap. Exhausted deliveries move to a dead-letter queue for inspection or manual replay.
Kash’s retry documentation describes the same pattern: retries on timeouts and 5xx with exponential backoff and jitter, a capped number of attempts, and manual replay for a limited retention window. Schedules differ per provider, so read yours.
On your side, plan for failure from day one:
- Make processing idempotent. Use the event ID with a database unique constraint so a retry never doubles the work.
- Keep your own dead-letter queue. Events that fail processing after you have acknowledged them need somewhere to wait, plus an alert so a person sees them.
- Write down the replay steps. Document how to redeliver missed events from the sender’s dashboard and how to reconcile state afterward.
- Return 503 on purpose when overloaded. A temporary failure beats a silent drop, because the sender will try again.
Deploying and monitoring webhook endpoints in production
A receiver that works in testing can still fail quietly in production. Cover these points before launch:
- Watch TLS. Use a CA-signed certificate and alert on expiry. An expired certificate makes every delivery fail at once.
- Check firewall rules. If your network restricts inbound traffic, allow the provider’s published IP ranges.
- Limit concurrency. Process jobs from a durable queue with a worker cap, so a burst of events cannot take your site down.
- Honor backpressure. When overloaded, respond with a retryable status and a
Retry-Afterheader if your sender respects it. - Track delivery success rate. Alert on a rising failure rate or an endpoint the sender has disabled.
- Keep delivery logs. Store event IDs, timestamps and outcomes, with secrets masked, so you can investigate and replay later.
Pro Tip: Rotate your signing secret on a calendar, not only after an incident. A practiced rotation is easier to manage; an unfamiliar one can cause disruption.
Code pattern: raw body, verify, dedupe, enqueue

A common implementation mistake is parsing the body before verifying it. In Express.js, express.json() consumes and re-serializes the body, so the bytes you hash are no longer the bytes the sender signed. Use express.raw({ type: 'application/json' }) on your webhook route instead, and parse JSON yourself after the signature passes.
The whole flow fits in a few lines of logic:
- Read the raw request bytes.
- Compute the HMAC with your secret and compare it to the signature header using a timing-safe function. Reject on mismatch.
- Check the timestamp against your tolerance window.
- Parse the JSON and insert the event ID into a table with a unique constraint. If the insert fails, return 200 and stop.
- Enqueue a durable job with the stored payload.
- Return 200.
Paste that list into your README as the contract every new webhook route must follow. Reviewers can then check a pull request against concrete steps instead of a vague idea of “secure”.
Our view on common webhook pitfalls
Webhook implementations can fail when they omit raw-body verification, idempotency, or a replay plan. Those gaps can cause problems during retries or forged requests. Our order of priority is simple: verify signatures first, enqueue before answering, and instrument delivery metrics. Build those patterns once and every later integration on your websites and apps inherits them.
Less manual integration wiring with WebsitePublisher
WebsitePublisher is the AI web platform: your AI assistant, whether that’s ChatGPT, Claude, Gemini or another tool, builds and publishes websites and apps on WebsitePublisher through the WebsitePublisher connector, and every change is live immediately, with no separate deploy step.
- Built-in integrations connect your site or app to services such as payments and email without custom glue code.
- API keys and signing secrets go into the vault in your dashboard, never into page code or the chat.
- Forms and data (entities) store the submissions and records your site or app works with.

Compare features and plans on the pricing page, or start building on the free plan.
FAQ
What is a webhook in simple terms?
A webhook is an automatic HTTP POST that one app sends to a URL you provide when something happens, such as a new order. It pushes data to you instead of making you ask for it. Red Hat’s webhook explainer covers common uses like payments, notifications and automation triggers.
How do I set up a webhook on my website?
Create an HTTPS endpoint that accepts POST requests, register its URL in the sender’s dashboard, and subscribe to the events you need. Verify the signature on the raw body, then queue the work and return a 2xx response. Vero’s setup overview describes the same basic flow.
How do I test a webhook without a live server?
Use a public inspector such as Webhook.site to get a temporary URL and watch headers, body and status codes arrive. For your own code, expose a local server with ngrok or cloudflared and replay saved payloads in unit tests.
Why am I receiving the same webhook twice?
Webhook delivery is at-least-once, so senders retry on timeouts and server errors, and duplicates are normal. Store each event ID with a unique database constraint so a repeat is detected and skipped.
What response code should my webhook endpoint return?
Return a 2xx status as soon as you have verified and queued the event. A 5xx or a timeout makes most senders retry, while most other 4xx codes are treated as permanent failures.
Sources
- Webhooks | UR Developer Docs
- Receiving webhooks without getting burned - DEV Community
- Webhooks: Delivery, Retries and Signature Verification | How HTTP Works
Recommended
- 3 Steps to Connect HubSpot to Your Website, CMS Recipes & API Guidance
- Small Teams: Ship a Client Site in Under an Hour With WebsitePublisher’s Claude
- Start with PayPal Buttons: When Devs & SMBs Should Use Orders v2
Build your site the same way
Describe what you want. Your AI builds and publishes it — with the full stack behind it: data, auth, forms, payments, integrations and hosting.
See how it works →
Website