Skip to content

Webhooks

Tracium fires webhooks when traceability events happen. Examples:

  • A trace event was recorded on-chain (event.recorded).
  • A new NFT/lot was minted (nft.created).
  • A lot was recalled (nft.recalled).
  • A custody transfer completed (custody.transferred).

This page covers payload structure, HMAC signature verification, retry semantics, and the available topics.

Topics currently available:

TopicWhen emitted
event.recordedA captured trace event was recorded on-chain
nft.createdAn NFT was minted (new lot)
nft.recalledAn NFT was recalled (TENANT_ADMIN action)
custody.transferredA custody transfer completed
webhook.testManually fired via POST /api/v1/webhooks/:id/test for testing

Every delivery is POST with application/json body:

{
"event": "event.recorded",
"timestamp": "2026-05-08T12:34:56.789Z",
"data": { ... }
}

Headers:

HeaderValue
X-Webhook-Idunique delivery UUID
X-Webhook-Eventthe topic (e.g. event.recorded)
X-Webhook-Signaturesha256=<hex-hmac>
Content-Typeapplication/json

Tracium signs every payload with HMAC-SHA256 over the raw body using the subscription secret (returned only at creation).

The header is sha256=<hex> (simple format, not Stripe-style with timestamp). Verify with constant-time comparison to prevent timing attacks.

import { createHmac, timingSafeEqual } from 'crypto';
function verify(req, secret) {
const header = req.headers['x-webhook-signature'];
// header format: 'sha256=<hex>'
const [, hex] = header.match(/^sha256=([a-f0-9]+)$/) || [];
if (!hex) throw new Error('invalid signature header');
const expected = createHmac('sha256', secret)
.update(req.rawBody)
.digest('hex');
if (!timingSafeEqual(Buffer.from(hex), Buffer.from(expected))) {
throw new Error('signature mismatch');
}
}
import hmac, hashlib
def verify(headers, raw_body, secret):
sig = headers['X-Webhook-Signature'] # 'sha256=<hex>'
if not sig.startswith('sha256='):
raise ValueError('invalid signature header')
received = sig[len('sha256='):]
expected = hmac.new(
secret.encode(),
raw_body,
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(received, expected):
raise ValueError('signature mismatch')
  • Tracium dispatches delivery via a managed queue with per-subscription concurrency.
  • If response is non-2xx, 3 retry attempts with exponential backoff (initial delay: 30s).
  • After 3 failures the delivery is marked failed, visible in GET /api/v1/webhooks/:id/deliveries with the captured error.

Make your endpoint idempotent. Use X-Webhook-Id as a dedup key since retries repeat the same id.

When you register an endpoint via POST /api/v1/webhooks, the URL must be valid + reachable. Standard server-side validation. HTTPS recommended.

Endpoints (all require TENANT_ADMIN role). Set BASE_URL to the API base URL provided in your provisioning email.

Ventana de terminal
# List tenant subscriptions
curl "$BASE_URL/api/v1/webhooks" \
-H "Authorization: Bearer $TOKEN"
# Create new subscription
curl -X POST "$BASE_URL/api/v1/webhooks" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.example.com/webhooks/darwin",
"events": ["event.recorded", "nft.created"]
}'
# Returns: { id, url, events, active, createdAt, secret }
# The secret is shown ONLY ONCE; store it now.
# Partial update
curl -X PATCH "$BASE_URL/api/v1/webhooks/$ID" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "active": false }'
# Delete
curl -X DELETE "$BASE_URL/api/v1/webhooks/$ID" \
-H "Authorization: Bearer $TOKEN"
# View last 100 deliveries
curl "$BASE_URL/api/v1/webhooks/$ID/deliveries" \
-H "Authorization: Bearer $TOKEN"
# Fire test event
curl -X POST "$BASE_URL/api/v1/webhooks/$ID/test" \
-H "Authorization: Bearer $TOKEN"