How do I verify webhook signature in Node.js?

Need to verify webhook signatures in Node. Docs mention HMAC-SHA256 but no code example. Payload I'm getting:

{
  "event": "work_order.status_changed",
  "timestamp": "2025-08-29T09:45:00Z",
  "data": { ... }
}

Header is X-FieldPulse-Signature. What's the exact algorithm? Raw body or JSON.stringify()? Case-sensitive key name?

Parents
  • Hey Carlos — yeah this one tripped me up too when I first looked at it. The signature is HMAC-SHA256 of the raw request body, not JSON.stringify(). FieldPulse signs the exact bytes as received, so you need to grab it before Express or whatever middleware parses it.

    Here's what works:

    const crypto = require('crypto');
    
    function verifySignature(rawBody, signature, secret) {
      const expected = crypto
        .createHmac('sha256', secret)
        .update(rawBody, 'utf8')
        .digest('hex');
      
      return crypto.timingSafeEqual(
        Buffer.from(expected, 'hex'),
        Buffer.from(signature, 'hex')
      );
    }

    Important bits:

    • Use rawBody — if you're using Express, you'll need something like express.raw({type: 'application/json'}) or a middleware that preserves the raw buffer
    • Secret is your webhook signing secret from Settings → Integrations → Webhooks
    • Always use timingSafeEqual — prevents timing attacks

    Edge case: if the payload has unicode escapes, the raw body vs. re-serialized JSON will differ. That's usually where verification fails silently. Stick to raw bytes and you're good.

    Docs reference: Setting Up Webhooks

Reply
  • Hey Carlos — yeah this one tripped me up too when I first looked at it. The signature is HMAC-SHA256 of the raw request body, not JSON.stringify(). FieldPulse signs the exact bytes as received, so you need to grab it before Express or whatever middleware parses it.

    Here's what works:

    const crypto = require('crypto');
    
    function verifySignature(rawBody, signature, secret) {
      const expected = crypto
        .createHmac('sha256', secret)
        .update(rawBody, 'utf8')
        .digest('hex');
      
      return crypto.timingSafeEqual(
        Buffer.from(expected, 'hex'),
        Buffer.from(signature, 'hex')
      );
    }

    Important bits:

    • Use rawBody — if you're using Express, you'll need something like express.raw({type: 'application/json'}) or a middleware that preserves the raw buffer
    • Secret is your webhook signing secret from Settings → Integrations → Webhooks
    • Always use timingSafeEqual — prevents timing attacks

    Edge case: if the payload has unicode escapes, the raw body vs. re-serialized JSON will differ. That's usually where verification fails silently. Stick to raw bytes and you're good.

    Docs reference: Setting Up Webhooks

Children