Your signer finished. Your webhook handler still has five ways to lie.
I build Putmysign, an e-signature tool with an API and embedded signing. This is a short version of the webhook guide on our site. The examples are from Putmysign, but the traps apply to other HMAC-signed webhooks too.
I build Putmysign, an e-signature tool with an API and embedded signing. This is a short version of the webhook guide on our site. The examples are from Putmysign, but the traps apply to other HMAC-signed webhooks too.
A webhook is just a POST to your server. Anyone who finds the URL can send one. Before I treat a document as complete, I need to check who sent the request, whether its bytes changed, and whether I've already handled it.
Trap 1: verifying the JSON you wish you received
The signature is calculated from the raw request body. Parse the JSON, turn it back into a string, and you may change whitespace, escaping or key order. Same object, different bytes. Different signature.
In Express, keep this route behind a raw-body parser, not your global JSON parser. In a Next.js route handler, read request.text() before parsing the JSON.
With Putmysign, the X-Signature header contains t and v1. The signed input is the timestamp, a dot, and the raw body. Verify that exact input with your webhook secret.
Trap 2: the "safe" comparison that crashes
Use a constant-time comparison, but check the buffer lengths first. Node's timingSafeEqual throws when the lengths differ.
A short or malformed signature should get rejected. It shouldn't turn your endpoint into a 500 generator.
Trap 3: a valid request from last week
A valid signature doesn't make a request fresh. A captured delivery can still have a perfectly good HMAC tomorrow.
Check the signed timestamp too. A five-minute tolerance is the example in our guide. Your server clock needs to be reasonably accurate. Refuse missing or malformed timestamp values, not just old ones.
Trap 4: doing the same work twice
Retries are normal. Putmysign retries failed deliveries five times over roughly a day, so the same event can arrive more than once.
Store the event ID. In production, use a durable unique constraint rather than a process-local Set. Two workers can see the same delivery at the same time.
Acknowledge quickly, then let a job do the slow work. If you acknowledge before the work runs, make sure your own queue can recover when that job fails.
Trap 5: one signer isn't everyone
recipient.signed means one person signed. It doesn't mean the document is finished.
For a multi-signer document, wait for document.completed before marking the whole contract complete. That's also when I want the finished PDF, with its signatures, signing certificate and audit trail. Keep a copy somewhere you control, not just a link.
The checklist
- Keep the raw bytes.
- Verify the HMAC and reject malformed headers.
- Check the timestamp.
- Deduplicate event IDs durably.
- Wait for the document-level completion event.
- Queue the slow work and keep the finished PDF.
The full guide has the Express and Next.js handlers, code and test cases:
https://putmysign.com/guides/verify-signed-webhooks-nodejs
Want to try the signing API? Start with the reference at https://putmysign.com/docs/api and use a test key while building. Test keys don't send email.
What do you do when the job behind a webhook fails after you've already returned 200?
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.