Validate the signature

How to confirm the notification came from us and was not changed on the way.

Anyone who finds your URL can send a POST to it. The signature is what separates a real notification from a fake one.

How it works

  1. Get the channel secret in the panel, under Notificações e logs (Show / Generate another). Keep it on the server as PROJECT_WEBHOOK_SECRET.
  2. Compute the HMAC-SHA256 of "{X-Project-Timestamp}.{raw body}" with the secret, in hex, and prefix it with sha256=.
  3. Compare it with X-Project-Signature-256 in constant time.
  4. Reject notifications older than 5 minutes (X-Project-Timestamp), so no one can replay an old one.
  5. Store the id of each notification and ignore one you have already processed.

Use the raw body

The signature covers the exact bytes that arrived. Parsing to JSON and turning it back into text changes spaces and key order, and the signature no longer matches — the most common mistake when integrating webhooks.

Examples

Node.js (Express)
const crypto = require('crypto');
const express = require('express');
const app = express();

// The RAW body (express.raw): the signature covers the exact text that arrived.
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const timestamp = req.get('X-Project-Timestamp') || '';
  const received = req.get('X-Project-Signature-256') || '';
  const expected = 'sha256=' + crypto
    .createHmac('sha256', process.env.PROJECT_WEBHOOK_SECRET)
    .update(timestamp + '.' + req.body)
    .digest('hex');

  const valid = received.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
  const recent = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300;
  if (!valid || !recent) return res.sendStatus(401);

  const event = JSON.parse(req.body);
  // event.id is the same on a retry: ignore what you already processed.
  res.sendStatus(204);
});

Rotating the secret

Under Notificações e logs, Generate another creates a new secret for the channel. Update PROJECT_WEBHOOK_SECRET on your server right after: from then on, notifications are signed with the new secret.

On this page