Body Verification (validate signature)

Routefusion signs all webhook payloads using Ed25519, allowing you to verify that a webhook was sent by Routefusion and that the body was not tampered with in transit.

Signature Headers

Every signed webhook request includes two headers:

  • X-Signature-Ed25519 — Hex-encoded Ed25519 signature
  • X-Signature-Timestamp — Unix timestamp (seconds) of when the webhook was signed

Verification Steps

  1. Extract the X-Signature-Ed25519 and X-Signature-Timestamp headers from the request.
  2. Concatenate the timestamp, a period (.), and the raw request body to form the signed message: {timestamp}.{raw_body}
  3. Verify the signature against Routefusion's public key using Ed25519.
  4. (Recommended) Reject requests where the timestamp is more than 5 minutes old to prevent replay attacks.

Public Key

Use the following public key to verify webhook signatures:

Production

-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAbKnJoUaBDFT7ZsM3Df/tO6YDL5dmp+aBEstnMg7J+O0=
-----END PUBLIC KEY-----

Sandbox

-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAgzAetDWJ6EzDjuq+zfOvotNlmBQhvgxwXxykb+xYdas=
-----END PUBLIC KEY-----

Examples

import crypto from 'crypto';

const PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAHrRp4HERTD+4o9jD8DZfxdGosOMhfdL/afGgLsZf2Ik=
-----END PUBLIC KEY-----`;

const publicKey = crypto.createPublicKey(PUBLIC_KEY_PEM);

function verifyWebhook(rawBody, signature, timestamp) {
  // Reject requests older than 5 minutes
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - parseInt(timestamp)) > 300) {
    return false;
  }

  const message = Buffer.from(`${timestamp}.${rawBody}`);
  const sig = Buffer.from(signature, 'hex');
  return crypto.verify(null, message, publicKey, sig);
}

// Express middleware example
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-signature-ed25519'];
  const timestamp = req.headers['x-signature-timestamp'];

  if (!verifyWebhook(req.body, signature, timestamp)) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(req.body);
  // Process webhook event...
  res.sendStatus(200);
});