Crossdeck Docs
Dashboard

Receiving Crossdeck webhooks — the live events, and how to verify them

Live ~15 min read · Updated Jul 28, 2026

When something a customer relies on changes — an access rule is added, a rule is lifted — your backend should react in milliseconds, not on the next cron. Outbound webhooks are how Crossdeck pushes those changes to your servers. This page is the contract: how to register an endpoint, how each event is signed, how to verify it in one line with the Node SDK, and the one rule that keeps it bank-grade — a webhook is a nudge, so you verify it and then read the authoritative state it points at.

Outbound webhooks are live.

Crossdeck delivers signed, retried, at-least-once webhooks to endpoints you register. The first event types on the wire are trust.rule.added and trust.rule.removed; new types ride the same spine, so the verification and reconcile pattern here covers all of them.

Not to be confused with inbound rail webhooks — those are how Apple, Google Play, and Stripe call into Crossdeck. The direction documented here is the opposite: Crossdeck calling out to your backend. For inbound rail webhooks, see Rail webhooks.

TL;DR

What ships today

CapabilityStatusWhere it lives
Outbound webhook delivery (Crossdeck → your backend) Live The outbound-event spine — registration, Secret Manager secret custody, canonical envelope, HMAC signer, at-least-once delivery + retry, SSRF guard, dead-letter — plus a scheduled drainer.
Live event types Live trust.rule.added, trust.rule.removed. New types ride the same spine as thin riders and verify identically.
Endpoint management (register / disable / rotate secret / delivery log + replay) Live Dashboard → Developers → Webhooks.
Signature verification in the SDK Live webhooks.constructEvent() (typed envelope) and verifyWebhookSignature() (parsed body) in @cross-deck/node. Constant-time, replay-bounded, rotation-aware.
Inbound rail webhooks (Apple / Google / Stripe → Crossdeck) Live Rail webhooks — how Crossdeck learns about your customers' subscription events. The opposite direction to this page.
Server-to-server reconciliation via Node SDK Live @cross-deck/nodegetCustomerEntitlements(), isEntitled(), listEntitlements(). The reconcile target the nudge points you at.

Verify a webhook — webhooks.constructEvent()

Receive the raw body (do not let a JSON body-parser mutate it before verification — the signature is over the exact bytes), pass it with the Crossdeck-Signature header and your endpoint secret. On success you get the typed WebhookEvent; on any failure — bad signature, replayed timestamp, missing header — it throws, and a throw means reject the request.

import { webhooks } from "@cross-deck/node";

app.post("/crossdeck-webhook", express.raw({ type: "application/json" }), async (req, res) => {
  let event;
  try {
    event = webhooks.constructEvent(
      req.body.toString("utf8"),          // the RAW bytes, unparsed
      req.headers["crossdeck-signature"],
      process.env.CROSSDECK_WEBHOOK_SECRET,     // from the dashboard, via env
    );
  } catch (err) {
    return res.sendStatus(401);              // bad sig / replay — do NOT act
  }

  // ACK fast so we don't retry a slow handler, then reconcile out of band:
  res.sendStatus(200);
  if (!seen(event.id)) {                        // idempotency — event.id may repeat
    const truth = await fetch(event.reconcile.url).then((r) => r.json());
    enforceFrom(truth);                          // the truth, never event.data alone
  }
});

Prefer the Stripe-shape client mount if you already hold a CrossdeckServercrossdeck.webhooks.constructEvent(...) is the same function. Both accept an array of secrets so you can rotate without dropping in-flight deliveries: pass [oldSecret, newSecret] until every endpoint has cut over.

Why this matters — push beats poll

The reason every bank-grade SaaS platform — Stripe, Auth0, Okta, Twilio, GitHub — ships outbound webhooks is that customers' backends genuinely need push semantics for some flows.

Live event types

This is the whole catalogue today. Every event shares the same envelope; only type and data differ. New types ride the same delivery spine — when one ships, it appears here and verifies with the exact same code.

TypeFires whenReconcile with
trust.rule.added A rule is added to your Trust blocklist (e.g. a block takes effect). GET /v1/trust/blocklist
trust.rule.removed A rule is lifted from your Trust blocklist. GET /v1/trust/blocklist

The envelope — identical shape for every type:

{
  "id": "evt_9f2c8a1b…",          // your idempotency key — dedupe on it
  "object": "event",
  "type": "trust.rule.added",
  "api_version": "2026-07-01",   // bumped only on a breaking envelope change
  "created": 1753700000,
  "livemode": true,               // false on a sandbox workspace
  "data": { "ruleId": "rul_7Q…" },  // the NUDGE — what changed, not the full state
  "reconcile": {                    // where the authoritative truth lives
    "method": "GET",
    "url": "https://api.cross-deck.com/v1/trust/blocklist"
  }
}

Delivery & retry

The nudge rule — reconcile, don't trust the payload

This is the one rule that keeps outbound webhooks bank-grade. The payload tells you what changed; it is never the authoritative value. After you verify the signature, GET the reconcile.url and act on what it returns — not on event.data alone.

Why it matters: at-least-once delivery plus retries means an event can arrive late, twice, or out of order. If you enforced straight off the payload, a replayed trust.rule.added could re-apply a rule you'd already lifted. Reading the reconcile endpoint collapses all of that to one question — "what is true right now?" — so ordering and duplication stop mattering. Verify → reconcile → enforce.

Manage endpoints

Everything endpoint-side lives in the dashboard under Developers → Webhooks:

Polling is still your safety net

Push is the fast path; it is not your only line of defence. Keep a low-frequency reconcile — getCustomerEntitlements() / getEntitlements() in @cross-deck/node, or GET-ing the same reconcile endpoints on a schedule — so that if an endpoint is down through every retry, your state still converges on the next poll. Belt and suspenders: webhooks for latency, a periodic poll for durability.


This page documents outbound webhooks that are live in production — the single source of truth for which events Crossdeck delivers and how to receive them. Last updated Jul 28, 2026.

Using an AI assistant? Read this page as clean markdown — index.md — or the whole docs index at /docs/llms.txt.