Ovianta Developers

Verifying signatures

Check that an event comes from Ovianta.

Every event carries an x-signature header: the HMAC-SHA256 of the raw request body, keyed with the endpoint's signing secret, encoded as lowercase hex. Use the whole secret as the key, whsec_ prefix included.

  1. Read the body exactly as received, before any JSON parsing.
  2. Compute the HMAC-SHA256 of it with the secret, as hex.
  3. Compare it with x-signature using a constant-time comparison.
  4. Answer 401 if they differ. Ovianta does not retry a 401.
import crypto from 'node:crypto';
import express from 'express';

const secret = process.env.OVIANTA_WEBHOOK_SECRET!; // whsec_...
const app = express();

// express.raw keeps the body as the exact bytes Ovianta signed.
app.post('/ovianta/events', express.raw({ type: 'application/json' }), (req, res) => {
  const expected = crypto.createHmac('sha256', secret).update(req.body).digest();
  const received = Buffer.from(req.get('x-signature') ?? '', 'hex');
  if (received.length !== expected.length || !crypto.timingSafeEqual(received, expected)) {
    return res.sendStatus(401);
  }

  const event = JSON.parse(req.body.toString('utf8'));
  // Store the event, answer, and process it afterwards.
  res.sendStatus(200);
});

Sign the raw bytes

Parsing the JSON and serializing it again changes spacing and key order, and the signature will not match.

Rotating the secret

Each endpoint has its own secret, and it cannot be changed. To replace it, add a new endpoint with the same URL and events, accept both secrets for a while, then delete the old endpoint. While both exist every event arrives twice, with the same eventId.

On this page