Dev.to Security 🔐 Cybersecurity 👁 0 📖 2 min read

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?

📰 Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.