Skip to content

A glossary story9 chapters · 6 min read

Replay Attack

Capturing a valid, correctly signed request, such as a webhook delivery or an API call, and resending it later so the receiver acts on it again.

Also called: Playback attack · Request replay · Webhook replay attack · Message replay

Reviewed

Follow along

Replay a captured webhook: does the signature catch it?

3 / 3

Incoming request

As signed

POST /webhook HTTP/1.1

X-Hub-Signature-256: sha256=1412882538d4c76d2943528221d5d0d839db50793d53dcbb3c264835ac39d256

632 bytes, exactly as the sender signed them.

Your server verifies

  1. Read the raw body and the headerUse the exact bytes received — 632 bytes, before any JSON parsing.
  2. Recompute the HMACHMAC-SHA256(secret, raw body)
  3. Compare the two digestsUse a constant-time comparison.
App secret (your server's copy)demo-app-secret

X-Hub-Signature-256 (received) vs HMAC-SHA256(secret, raw body) (computed)

Receivedsha256=1412882538d4c76d2943528221d5d0d839db50793d53dcbb3c264835ac39d256
Computedsha256=1412882538d4c76d2943528221d5d0d839db50793d53dcbb3c264835ac39d256
Match — signature validThe body is byte-for-byte what the sender signed. Process the event.
This delivery was captured and resent days later. Left untouched it still passes the X-Hub-Signature-256 check; only an edited body fails. That is why deduplication by wamid must sit after the signature check.

Match — signature valid. The body is byte-for-byte what the sender signed. Process the event.

Chapter 1 · The scenario

Example: A voucher bot replayed in Indore

An electronics retailer in Indore runs a WhatsApp bot that issues a store-credit voucher when a customer taps "Yes, refund to wallet" on an interactive message. During integration testing, an engineer pasted real webhook deliveries, headers included, into a public request-inspection site. Weeks later someone resent one of those deliveries 40 times. Every copy passed the X-Hub-Signature-256 check, and because the handler deduplicated nothing, it issued 40 vouchers to one account before finance noticed. The fix took a day: an atomic insert of each inbound wamid into a dedup store with an eight-day expiry before any action, a rule that drops events older than eight days, a rotated app secret so the leaked copies no longer verify, and a separate test app whose secret never touches production.

— A voucher bot replayed in Indore

Chapter 2 · What is a replay attack?

A replay attack is an attack in which someone captures a legitimate message (a signed webhook delivery, an authenticated API request, a step in a login exchange) and sends it again later, unchanged. Because nothing has been altered, every integrity check passes: the signature is genuine, the token is valid and the JSON is well formed. What makes it an attack is repetition at the wrong time: a refund credited twice, an order confirmed again, a one-time action triggered on demand by someone who was never meant to trigger it.

This is the gap a Hash-Based Message Authentication Code (HMAC) leaves open. Meta signs every WhatsApp webhook in the X-Hub-Signature-256 header with an HMAC-SHA256 of the raw body keyed with your App Secret, which proves the body came from Meta and was not modified. The signature covers the body only, though: there is no signed delivery timestamp and no nonce, so a copy replayed next week verifies exactly as it did the first time. Copies are rarely sniffed off the wire, because TLS protects transit; they leak from verbose request logs, debugging proxies, public request-inspection tools used during Webhook Testing and fixture files committed with real headers.

Defence therefore happens after the signature check. Idempotent processing with Webhook Deduplication, keyed on the event's identity such as the WhatsApp Message ID (wamid) plus its status, turns a replayed event into a harmless no-op. Freshness checks on the event timestamp reject anything older than a genuine retry could be, and in request formats you control, a signed timestamp plus a single-use nonce shrinks the usable window to minutes. A replay attack is a hostile resend; do not confuse it with webhook replay, the deliberate reprocessing of your own stored events after a bug fix, which relies on the same idempotency for the opposite purpose.

Key takeaways

  • A replay attack resends a captured, genuine request, so the receiver repeats an action the real sender asked for only once.
  • A correct HMAC does not stop a replay: the copied body and signature are untouched, so X-Hub-Signature-256 still verifies.
  • Meta's webhook signature covers only the raw body, with no signed timestamp or nonce, so deduplication by event identity is your main defence.
  • Meta retries webhooks for up to 7 days, so a tight freshness window on event timestamps would drop genuine retries; keep dedup keys longer than that.
  • In signature schemes you design yourself, sign a timestamp and a nonce with the body and reject anything stale or already seen.

Chapter 3 · How it unfolds

How does a replay attack work?

  1. A genuine request is captured

    The attacker obtains a real delivery, the raw body plus its X-Hub-Signature-256 header, typically from full request logs, a misconfigured proxy, a public request bin used for testing or a leaked fixture file.

  2. It is resent unchanged

    Hours or days later they POST the identical bytes and header to your endpoint. Nothing has to be forged, because nothing is modified, and the app secret is never needed.

  3. The signature still verifies

    Your server recomputes HMAC-SHA256 over the raw body with the app secret and gets the same digest as the original. A signature check alone cannot tell a copy from the first delivery.

  4. Deduplication stops the repeat

    Before any side effect, record the event's identity atomically: the wamid for an inbound message, the wamid plus status for a status update. If the key already exists, return 200 and do nothing else.

  5. Freshness and ordering close the gaps

    Reject events whose timestamp lies in the future or beyond Meta's 7-day retry window plus a margin, never move a message's status backwards, and expire dedup keys only after that window has passed.

Chapter 4 · In code

A replay attack in code

import crypto from "node:crypto";
import { redis } from "./redis.js"; // ioredis client
const MAX_AGE_S = 8 * 24 * 3600; // Meta retries for up to 7 days; add a margin
function validSignature(rawBody, header = "") {
const expected = "sha256=" + crypto
.createHmac("sha256", process.env.APP_SECRET)
.update(rawBody)
.digest("hex");
return header.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}
export async function handleWebhook(req, res) {
// 1. Authenticity: proves Meta signed these bytes, NOT that they are new.
if (!validSignature(req.rawBody, req.get("X-Hub-Signature-256"))) return res.sendStatus(401);
const now = Math.floor(Date.now() / 1000);
for (const entry of req.body.entry ?? []) {
for (const { value = {} } of entry.changes ?? []) {
const events = [
...(value.messages ?? []).map((m) => ({ key: `msg:${m.id}`, ts: Number(m.timestamp), data: m })),
...(value.statuses ?? []).map((s) => ({ key: `st:${s.id}:${s.status}`, ts: Number(s.timestamp), data: s })),
];
for (const ev of events) {
// 2. Freshness: nothing from the future, nothing older than any genuine retry.
if (ev.ts > now + 300 || now - ev.ts > MAX_AGE_S) continue;
// 3. Deduplication: atomic "first writer wins", so a replay becomes a no-op.
const first = await redis.set(`wa:${ev.key}`, "1", "EX", MAX_AGE_S, "NX");
if (first === "OK") await queue.add("whatsapp-event", ev.data);
}
}
}
return res.sendStatus(200); // answer replays the same way: they learn nothing
}

The signature check runs first, but it is the atomic SET NX on the event key that actually stops a replay.

Chapter 5 · Where it goes wrong

Common mistakes with a replay attack

  • Treating a valid signature as proof of freshness

    An HMAC proves who signed the bytes and that they are unchanged, nothing more. Put deduplication and freshness checks after the signature check and before any side effect.

  • A five-minute window on WhatsApp event timestamps

    The event timestamp records when the event happened, not when it was delivered, and Meta retries for up to seven days. A tight window silently drops genuine retries after an outage; use a window longer than the retry period and rely on deduplication.

  • Check-then-insert deduplication

    Reading "have we seen this?" and writing the key later lets two concurrent copies both pass. Use one atomic operation, such as SET NX or an insert on a unique key, and act only if your write won.

  • Leaking replayable requests yourself

    Full request logs, public request bins and fixtures with real signatures hand out ready-made replays. Mask logs, test with a separate app and secret, and rotate the app secret if real deliveries were exposed, which makes the leaked copies fail verification.

  • Expiring dedup keys too early

    A key kept for an hour lets tomorrow's replay through. Keep keys at least as long as the oldest event your freshness check still accepts.

Chapter 6 · Why it matters

Why does a replay attack matter?

Most webhook handlers cause side effects: issuing a refund or voucher, confirming a booking, sending a follow-up template, moving a CRM deal, crediting a wallet. If a replayed delivery is treated as new, anyone holding one old payload, or an engineer pasting a fixture into production, can repeat those effects at will, and every extra outbound template is another charged message. A signature check answers only "did Meta send these bytes?"; replay protection answers "have we already acted on them?", and that is the question that protects money, stock and customers' trust.

Chapter 7 · The facts

A replay attack: key facts and limits

Meta webhook signatureX-Hub-Signature-256: HMAC-SHA256 of the raw body
Signed timestamp or nonce (Meta)None: the signature covers the body only
Meta webhook retriesDecreasing frequency, up to 7 days
Dedup keywamid for messages; wamid + status for statuses
Dedup key lifetimeLonger than the 7-day retry window, e.g. 8 days
Event timestamp formatUnix seconds, sent as a string
Typical tolerance in schemes you designAbout 5 minutes
NonceAt least 128 random bits, accepted once

Chapter 8 · Replay Attack FAQs

Replay Attack FAQs

Does HMAC signature verification prevent replay attacks?+
No. An HMAC proves the body is unchanged and came from whoever holds the secret, but a byte-for-byte copy carries the same valid signature. Add deduplication, freshness checks or a signed nonce on top.
How do I stop replay attacks on WhatsApp webhooks?+
Verify `X-Hub-Signature-256`, then deduplicate atomically on the event's identity before any side effect: the `wamid` for messages, the `wamid` plus status for statuses. Also reject events older than Meta's 7-day retry window and keep dedup keys for at least that long.
What is the difference between a replay attack and webhook replay?+
A replay attack is a hostile resend by someone else; webhook replay is you deliberately reprocessing stored events, for example after fixing a bug. Both depend on idempotent handlers, so a repeated event never repeats its side effects.
Should I use a nonce or a timestamp to prevent replays?+
Use both when you control the format. A signed timestamp limits how long a captured request stays usable, and a nonce remembered for that window makes each request single-use inside it.
Does TLS stop replay attacks?+
Only on the wire. TLS stops attackers reading or re-injecting traffic in transit, but once a request is logged, proxied or leaked outside the connection, only application-level checks can tell a copy from the original.

Chapter 9 · What good teams do

Best practices for a replay attack

  • Return 200 for a detected duplicate without processing it, so a replayer cannot tell what happened.
  • Alert on bursts of duplicate keys from one source: it is either an attack or a broken retry loop.
  • Make side effects idempotent as a second line of defence, for example with an idempotency key on refunds.
  • Single-use OTPs and login challenges are replay defences too; mark them used the moment they verify.

From definition to your first message

InfiQ is an official Meta Business Partner. Start a 7-day free trial — no card needed — and see this running on your own number.