Overview

A webhook endpoint is a public URL. Anyone who finds it can send it a request that looks exactly like a real event, and if your code acts on it, a stranger can mark orders as paid or cancel bookings.

A signature on every event closes that door: your code repeats the sender’s computation and acts only when the two results match.

How signing works

Each endpoint has its own signing secret, shown once when the endpoint is created. The secret never travels with the events: only what it produces does.

A signature covers the moment the event was sent and its body, so a request captured on the way cannot be replayed hours later.

The signature header

Every event carries a header with a timestamp and one or more signatures, separated by commas. Each signature is an HMAC-SHA256 of the timestamp, a full stop and the raw body.

Rotating the secret

Rotate a secret when someone who knew it leaves the team, when it may have been logged by accident, or simply once a year, and choose how long the old one keeps working.

Two secrets at once

While both secrets are valid, every event is signed with each of them. Code that accepts any of the signatures keeps working through the change, without a single rejected event.

Verify a request

Verification comes before anything else in your handler: before parsing, before logging the body, before touching the database. Anything that fails gets an error and no further work.

With the SDK

The official libraries do the whole check in one call, including the tolerance on the timestamp and the secrets in rotation, and throw an error when it fails.

By hand

Without a library the check is short enough to write yourself. Compare the signatures in constant time, and check every one in the header, not just the first.

Retries and replays

An event counts as delivered when your endpoint answers with a success status within ten seconds. Anything else, a timeout included, is a failure, and the event is sent again later.

The retry schedule

Failed events are tried again seven times over about a day and a half, waiting longer each time. Each new attempt is signed again with a fresh timestamp.

Rejecting old events

Reject events whose timestamp is more than five minutes away from your server’s clock: a request recorded on the way is then useless almost as soon as it is captured.

Test locally

The command line tool forwards real, signed events to a URL on your own machine, with a temporary secret for the session, so a failed check shows up at once.

Going live

Before switching to live mode, make sure the raw body is verified first, the live secret sits in your secret manager, old events are rejected and slow work goes to a queue.

From then on your endpoint acts only on events that are genuine.