VORIM
We use cookies

We use cookies to analyze site traffic and improve your experience. You can choose to accept all cookies or only essential ones. See our Privacy Policy.

Webhooks

Alert rules deliver over HTTP with an HMAC signature. How to verify one, what the retry policy is, and why a replay window matters more than it sounds.

Webhooks

Vorim POSTs to your endpoint when an audit event matches an alert rule you have configured. The body carries the rule that matched and the event that matched it, so you can route on either without a second lookup.

What arrives

Four headers travel with every delivery. Content-Type is application/json, X-Vorim-Event repeats the event type so you can route before parsing, X-Vorim-Timestamp is the ISO 8601 time the body was signed, and X-Vorim-Signature carries the HMAC. The signature header is present only when the deployment has a signing secret configured.

POST https://your-endpoint.example.com/vorim
Content-Type: application/json
X-Vorim-Event: tool_call
X-Vorim-Timestamp: 2026-08-31T09:14:02.118Z
X-Vorim-Signature: sha256=9f2b...c41d

{
  "rule_id": "rule_7f3a2b",
  "rule_name": "Denied transactions",
  "event": {
    "event_type": "tool_call",
    "agent_id": "agid_acme_b5f35578",
    "action": "invoice.refund",
    "result": "denied"
  },
  "timestamp": "2026-08-31T09:14:02.118Z"
}
typescript

Verify the signature

Recompute the HMAC and compare it in constant time. Verify against the raw request body, never against JSON you have parsed and re-serialised, because a round trip through your JSON library will reorder keys or change number formatting and the MAC will not match.

Reject anything whose timestamp is more than 300 seconds from now. That window is wide enough for ordinary clock skew and narrow enough to make a replayed delivery useless.

import { createHmac, timingSafeEqual } from 'node:crypto';

// You need the RAW body: a JSON round trip changes the bytes the MAC covers.
app.post('/vorim', express.raw({ type: 'application/json' }), (req, res) => {
  const timestamp = req.get('X-Vorim-Timestamp') ?? '';
  const received  = req.get('X-Vorim-Signature') ?? '';

  const age = Math.abs(Date.now() - Date.parse(timestamp)) / 1000;
  if (!timestamp || Number.isNaN(age) || age > 300) return res.sendStatus(400);

  const expected = 'sha256=' + createHmac('sha256', process.env.VORIM_WEBHOOK_SECRET)
    .update(timestamp + '.' + req.body.toString('utf8'))
    .digest('hex');

  const a = Buffer.from(expected);
  const b = Buffer.from(received);
  if (a.length !== b.length || !timingSafeEqual(a, b)) return res.sendStatus(401);

  const event = JSON.parse(req.body.toString('utf8'));
  res.sendStatus(200);   // acknowledge fast, process out of band
});
typescript

What we do not promise

Delivery is at most once. There is no retry, no backoff and no dead-letter queue: a delivery that times out after 10 seconds, or returns any error, is logged on our side and dropped. Do not build a system whose correctness depends on receiving every webhook. Treat them as a prompt to reconcile, and use the audit query or export API as the source of truth.
Need Help?

Book a demo for a walkthrough, or contact us for support. For enterprise needs, reach out at sales@vorim.ai.