Server-to-server

Receive guardrail actions safely.

Traket detects a rolling usage breach, commits the incident and delivery atomically, then calls your HTTPS endpoint. Your backend decides how to notify, flag, rate-limit, restrict, or ban the customer.
On this page

Endpoint contract

Use a public HTTPS endpoint on port 443. Redirects are not followed. URLs may not contain credentials or fragments, and DNS must resolve only to public addresses—private, loopback, link-local, and reserved targets are blocked.

Store the per-action signing secret shown after creation or rotation. Traket does not show that plaintext secret again.

Traket-Delivery-Id
Stable delivery UUID. Use it as the idempotency key for the action.
Traket-Event
guardrail.triggered or guardrail.test.
Traket-Action
notify, flag, rate_limit, restrict, or ban.
Traket-Timestamp
Unix seconds included in the signed message.
Traket-Signature
v1=<hex HMAC-SHA256>.
Content-Type
application/json.
User-Agent
Traket-Guardrails/1.0.

Verify before acting

Compute HMAC-SHA256 over the exact raw request body using timestamp.body. Reject timestamps outside a five-minute tolerance, compare signatures in constant time, then deduplicate by delivery ID in the same transaction that changes customer state or enqueues a durable action.

Next.js / Node.js
import { createHmac, timingSafeEqual } from "node:crypto";

export async function POST(request: Request) {
  const body = await request.text(); // verify the exact raw body
  const timestamp = request.headers.get("traket-timestamp") ?? "";
  const signature = request.headers.get("traket-signature") ?? "";
  const deliveryId = request.headers.get("traket-delivery-id") ?? "";
  const timestampSeconds = Number(timestamp);

  if (!deliveryId || !Number.isSafeInteger(timestampSeconds) ||
      Math.abs(Date.now() / 1000 - timestampSeconds) > 300) {
    return new Response("Stale signature", { status: 401 });
  }

  const expected = `v1=${createHmac(
    "sha256",
    process.env.TRAKET_ACTION_SECRET!,
  )
    .update(`${timestamp}.${body}`)
    .digest("hex")}`;
  const supplied = Buffer.from(signature);
  const valid = supplied.length === Buffer.byteLength(expected) &&
    timingSafeEqual(supplied, Buffer.from(expected));

  if (!valid) return new Response("Invalid signature", { status: 401 });

  const event = JSON.parse(body);
  if (event.test) return new Response(null, { status: 204 });

  // Commit the idempotency record and the customer action—or a durable
  // job for it—in one transaction. Repeated delivery IDs are no-ops.
  await applyCustomerActionOnce({
    deliveryId,
    customerId: event.customer.externalId,
    effect: event.action.effect,
  });

  return new Response(null, { status: 204 });
}
Parse JSON only after the raw body has passed signature and timestamp verification. Re-serializing the payload before verification changes the signed bytes.

Delivery and retries

Return 2xx after durable work

Any 2xx response completes the delivery. Queue your own durable job first when enforcement takes longer than the configured endpoint timeout.

Expect at-least-once delivery

Timeouts, transport errors, 408, 425, 429, and 5xx responses retry with bounded exponential jitter. Other 4xx responses are terminal.
  • Endpoint timeouts are configurable from 1 to 10 seconds and default to 5 seconds.
  • Actions support 1 to 10 automatic attempts and default to 6.
  • Traket discards response bodies so your receiver cannot write prompts, outputs, or secrets into delivery diagnostics.
  • Test events use the same signature and delivery rules. Return 2xx without changing customer state when event.test is true.

Overlap secrets during rotation

Queued deliveries move to the new signing secret immediately, but an attempt already in flight can finish with the previous signature. Briefly accept both the new and previous secret, then retire the old value after the overlap window.

Keep versions explicit

Store the current and previous secret as separate versioned values so rollout and retirement are deliberate.

Verify either, act once

Signature rotation never changes the idempotency rule. A repeated Traket-Delivery-Id must remain a no-op.

Triggered event payload

The customer external ID is the same imported Stripe customer ID sent through the Traket SDK. Optional rule filters and source-event fields are omitted when they were not supplied.

guardrail.triggered
{
  "schemaVersion": 1,
  "id": "delivery_uuid",
  "type": "guardrail.triggered",
  "createdAt": "2026-07-13T18:30:00.000Z",
  "projectId": "project_uuid",
  "action": {
    "id": "action_uuid",
    "effect": "rate_limit"
  },
  "rule": {
    "id": "rule_uuid",
    "name": "Free plan request burst",
    "scope": "plan",
    "metric": "request_count",
    "threshold": 100,
    "currency": "usd",
    "windowSeconds": 300
  },
  "incident": {
    "id": "incident_uuid",
    "observedValue": 101,
    "windowStartedAt": "2026-07-13T18:25:00.000Z",
    "windowEndedAt": "2026-07-13T18:30:00.000Z"
  },
  "customer": {
    "id": "traket_customer_uuid",
    "externalId": "cus_29ff",
    "planId": "plan_uuid"
  },
  "sourceEvent": {
    "id": "usage_event_uuid",
    "occurredAt": "2026-07-13T18:30:00.000Z",
    "provider": "openai",
    "model": "gpt-5.4-mini",
    "feature": "agent_run",
    "environment": "production",
    "status": "success"
  }
}

Usage events that can trigger this delivery enter through POST /api/v1/usage/events.