IntegrAuth Academy
Identity, security and practical AI — free, from zero. Byte-sized lessons, real diagrams, hands-on labs, zero fluff. Two programs, each with its own exam and certificate.
English is hard? Your browser can translate this page into Tamil, Telugu, Kannada, Malayalam or Hindi. Tech words stay in English so you learn the real names.
How?
- Chrome on phone: ⋮ menu → Translate
- Chrome on computer: right-click → Translate to …
- Edge: the translate icon in the address bar
Signed requests and webhooks
A payment provider calls Bot A's server: "Maya's ₹500 payment worked. Ship her order." Anyone on the internet can send that same message. Your goal: check that a message is real, unchanged and new, before you act on it.
- HMAC uses a shared secret to make a tag. Changing one byte changes the tag.
- Sign a canonical request: method, path, query, headers, time and body hash.
- A valid signature does not prove the message is new. Check the time and the id.
- Check webhooks over the raw bytes, and handle retries once.
- Use a key pair when a third party must check who sent a message.
a "payment received" SMS at a small shop. Anyone can type that message, so careful owners check the bank app. A webhook signature is that check, built into the message. Only the real bank can make the right seal. The owner also asks: is this message new, or an old screenshot shown again?
HMAC: a seal made from a shared secret
HMAC (hash-based message authentication code) takes a secret key and a message. It runs them through a hash function such as SHA-256 and returns a short tag. Anyone with the same secret can compute the tag again and compare. Without the secret, you cannot make a valid tag. Change one byte of the message, and the tag is completely different.
HMAC is symmetric: both sides hold the same secret. So it only proves "someone who knows the secret wrote this". The receiver could have written it too. When a third party, or many parties, must check who sent a message, use a key-pair signature. The private key signs. Anyone can check with the public key. With HMAC, even a secret for each customer cannot prove authorship to an auditor.
Sign the whole request: the canonical request
What you sign matters as much as how. Sign only the body, and an attacker can change the query string, such as amount=5 to amount=500. So sign a canonical request. This is a strictly formatted summary of the parts that matter. It holds the method, the path, the sorted query, chosen headers, the timestamp and a hash of the body. The server builds it again from what it really received. If any part changed on the way, the tags differ.
Real is not the same as fresh
A valid signature proves a message is genuine. It does not prove the message is new. An attacker can capture a signed "refund ₹500" request and send it again. This is a replay attack. Two defenses work together:
- A timestamp inside the signed content. Reject anything older than a few minutes (five is common). The attacker cannot refresh a signed timestamp. A window that is too tight rejects honest requests from drifting clocks.
- A unique id or nonce (a number used once). The receiver remembers it for the length of the window and rejects repeats.
Also compare tags with a constant-time function, such as hmac.compare_digest in Python. A plain == stops at the first different character. How long it takes can leak how much of a guess was right.
Webhooks: checking what comes in
A webhook is an HTTP callback: when something happens, a provider sends a POST to a URL you registered. The URL is public, so the signature is its lock. The Standard Webhooks pattern uses three headers: a unique message id, a timestamp and the signature. The signed content is id.timestamp.body. Five habits:
- Verify over the raw body, before you parse. Re-written JSON can differ in spacing or key order, so a real message fails.
- Check the timestamp against your window.
- Make retries harmless (be idempotent). Providers retry when you are slow. A retry has a valid signature and a fresh time, so Bot A could ship twice. Record each id when work starts. If an id repeats, return 2xx and do nothing. This also protects retried API calls (idempotency keys).
- Rotate without an outage. The sender signs with both old and new secrets and sends both. Accept the message if any one matches.
- Answer fast. Send a 2xx quickly and do slow work from a background queue. Slow answers cause retries.
A signature proves who sent a message and that nobody changed it. It does not prove the sender may ask for this. Keep the secret out of logs. A secret in a debug line is a forgery kit.
Go deeper: Length extension and the canonical list
Use the real HMAC construction. Never invent sha256(secret + message). With hashes like SHA-256, that lets an attacker add data and compute a valid tag without the secret. This is a length-extension attack. HMAC's two nested hashing passes close the hole.
A canonical request holds: the HTTP method, the path, the query sorted by name, chosen headers (lowercase, trimmed, sorted, including host and timestamp), the list of signed headers, and a SHA-256 hash of the body. That text, plus the timestamp and a scope, goes through HMAC. The result travels in the Authorization header with a key id. Some schemes derive a separate signing key for each day and service, so a stolen key works only in a narrow scope.
Go deeper: Sending webhooks: the SSRF trap
When your platform sends webhooks, customers type in the URL. An attacker can type an address that points inward, such as the cloud metadata address 169.254.169.254 or localhost. Your server then becomes a messenger into your own network. This is server-side request forgery (SSRF). More ways APIs break covers it. Defenses:
- Look up the host name and refuse private, loopback, link-local and metadata addresses when you connect.
- Do not follow redirects blindly.
- Send from an isolated worker with no route to internal systems.
- Sign what you send, with a separate secret for each customer.
Go deeper: A standard way to sign HTTP messages
| HMAC | Key-pair signature | |
|---|---|---|
| Keys | One secret, held by both sides | Private key signs, public key checks |
| Can the receiver forge it? | Yes | No |
| Many checkers | Each needs the secret | Publish the public key, like a JWKS |
| Speed | Very fast | Slower, needs key distribution |
HTTP Message Signatures (RFC 9421, February 2024) sign HTTP messages with either kind of key. A Signature-Input header lists what is covered, such as @method, @path, chosen headers and a Content-Digest header for the body. It also holds created, expires, nonce and keyid. The Signature header carries the result. Algorithms include HMAC-SHA256, Ed25519, ECDSA P-256 and RSA-PSS. The standard does not force replay protection. The checker must look at created and nonce.
🧪 Interactive lab — enable JavaScript to play with this one.
Run printf '%s' 'msg_1.1790000000.{"amount":500}' | openssl dgst -sha256 -hmac 'demo-secret'. Change one character and run it again. The tag changes completely. Paste the same secret and message into the lab above to get the same tag. For the full pattern, read the open Standard Webhooks specification.