Safeguard
Developer Tools•

Verifying payment webhooks: signatures, timestamps and replays

A payment webhook endpoint is a public door that moves money state. How to verify the sender, reject replays and avoid acting twice on the same event.

Safeguard Security Team
3 min read

A payment webhook is an HTTP request from your provider that says a payment succeeded, failed or was refunded. Anyone who can reach the URL can fake one. Verify a signature over the exact raw body, check a timestamp, and deduplicate events before you change any order or balance.

Why this matters more for payments

If a forged webhook can mark an order as paid, an attacker gets goods for free. If it can mark a refund as complete, your ledger drifts from the provider's. The endpoint is public by design, so the request itself has to prove who sent it.

Step 1: verify a signature over the raw body

Most providers sign the payload with a shared secret or a private key and send the signature in a header. Follow their documented scheme exactly. Three habits prevent most bugs:

  • Compute the signature over the raw bytes you received, before any JSON parsing or reformatting.
  • Compare with a constant time function.
  • Reject on any mismatch, with a generic response.

RFC 9421 describes a general standard for HTTP message signatures. It lets a signer cover chosen parts of a message and carry the result in Signature-Input and Signature headers, and it defines optional created and nonce parameters for timing and uniqueness. Many providers use their own scheme, so follow theirs unless they say they use the RFC. I did not read the RFC's replay section in full, so check section 7.2.2 yourself for its guidance.

Step 2: bound the time

A valid signature on an old request is still a valid signature. Include a timestamp in the signed data, and reject requests outside a short window, for example a few minutes. Keep your server clock in sync so honest requests are not rejected.

Step 3: deduplicate by event ID

Providers retry webhooks when your endpoint is slow or returns an error, so duplicates are normal even without an attacker. Store each event ID, and make the handler skip IDs you have processed. Do this in the same database transaction that updates the order, so two parallel deliveries cannot both win.

Step 4: trust but confirm

For high impact events, fetch the payment from the provider's API using your own credentials before you fulfil. A webhook then acts as a nudge, and the API call is the source of truth.

Operational notes

  1. Keep the signing secret in a secrets manager, and rotate it on a schedule and after any suspected leak.
  2. Accept both old and new secrets during a rotation.
  3. Respond quickly with a success code and do heavy work in a queue.
  4. Log event IDs and verification failures, never full card or account data.
  5. Alert on a rise in signature failures, which can signal probing.

See also verifying creator payout webhooks for a related walk through.

Quick check

Is an IP allowlist enough instead of a signature?

No. Allowlists help as an extra layer but addresses change and can be shared. Verify the signature too.

Why store event IDs if the signature is valid?

Valid events are still delivered more than once. Deduplication stops double fulfilment.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.