Relativity6Platform Docs

Webhooks

Beta

Receive a signed callback when a risk job reaches a terminal state, instead of polling.

Beta

Risk API v2 is in beta. You can use it in production, but request fields, response shapes, category coverage, and credit weights may change without a version pin. Build against the documented contract and expect updates.

Overview

Register an HTTPS endpoint and we'll POST a signed event to it whenever one of your jobs reaches a terminal state. Two events are delivered:

EventSent when
job.completedA job finished successfully.
job.failedA job stopped with an error.

Webhooks are scoped to your organization — you only receive events for jobs you submitted. They complement, not replace, polling: you can still call GET /v2/risk/{job_id} at any time.

Register a webhook

Create a subscription with the HTTPS URL to call. Optionally restrict it to specific events:

POST /v2/webhooks

The response includes a signing secret. It is shown only once — store it securely; you'll use it to verify every delivery.

Created

Delivery payload

Each delivery is a POST with a JSON body and these headers:

HeaderDescription
X-R6-EventThe event type (job.completed or job.failed).
X-R6-Webhook-IdStable delivery id — use it to deduplicate retries.
X-R6-TimestampUnix-seconds timestamp the signature was computed at.
X-R6-SignatureHex HMAC-SHA256 signature (see below).

POST to your endpoint

Webhook delivery bodies use camelCase field names (jobId, pollUrl, and so on). Use pollUrl to fetch the full job result via the poll endpoint; the completed response object inside that poll uses snake_case wire fields (job_id, metadata, categories, source_ids, and so on).

Verify signatures

Every request is signed so you can confirm it came from Relativity6. The signature is HMAC-SHA256 over the string `${timestamp}.${rawBody}` using your subscription secret, hex-encoded.

To verify: read X-R6-Timestamp and X-R6-Signature, recompute the HMAC over the raw request body, and compare in constant time.

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

function verifyR6Webhook(rawBody, headers, secret) {
  const timestamp = headers["x-r6-timestamp"];
  const signature = headers["x-r6-signature"];

  // Reject stale deliveries (5-minute replay window).
  const ageSeconds = Math.abs(Date.now() / 1000 - Number(timestamp));
  if (!timestamp || ageSeconds > 300) return false;

  const expected = createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");

  const a = Buffer.from(expected);
  const b = Buffer.from(signature ?? "");
  return a.length === b.length && timingSafeEqual(a, b);
}

Use the raw body

Compute the HMAC over the exact bytes you received, before any JSON parsing or re-serialization. Re-stringifying the body can change whitespace or key order and break verification.

Reliability

  • Respond fast with a 2xx. Acknowledge receipt, then do your processing asynchronously. Non-2xx responses are retried.
  • Expect retries and duplicates. Deliveries are retried on failure, so the same event may arrive more than once. Deduplicate on X-R6-Webhook-Id.
  • Treat the webhook as a trigger. The authoritative result always lives at pollUrl; fetch it if you need the full payload.

Manage subscriptions

The list view never returns the signing secret. If you lose a secret, delete the subscription and create a new one.