The client check is a courtesy; the server check is the law
Gating in the browser (the isEntitled you used to hide a button) is the right call for UI — it's fast and it makes the experience clean. But it lives on the user's machine, and a determined person can flip it. So the rule from Verify your setup applies in full here: anything that costs you money or exposes private data must be checked on the server.
The Node SDK is that server-side check — plus error capture for your backend and signature verification for inbound rail webhooks. One package, the three server jobs.
CrossdeckServer, with your secret key
Install the package, create a server client with your secret key, and check entitlements where they can't be tampered with:
// npm install @cross-deck/node
import { CrossdeckServer, verifyWebhookSignature } from "@cross-deck/node";
const server = new CrossdeckServer({ secretKey: process.env.CROSSDECK_SECRET_KEY });
// gate on the server, where the client can't lie about it
if (await server.isEntitled({ userId }, "pro")) {
// …run the expensive / privileged operation…
}
And verify inbound rail webhooks before you trust them — a forged webhook should never grant access:
const event = verifyWebhookSignature(rawBody, req.headers["crossdeck-signature"], process.env.CROSSDECK_WEBHOOK_SECRET);
// throws on a bad signature — only verified events get here
Same truth, authoritative read
The server client authenticates with your secret key — never the publishable one that ships to browsers — and reads the same entitlement truth your dashboard does. isEntitled({ userId }, key) resolves the user through the very same resolver every webhook and event uses, and returns their current, authoritative access — not a warm client-side cache that could be stale or spoofed.
The same client captures server-side errors into the same Issues board as your front end, so a 500 in your API and a crash in your UI land in one place. And verifyWebhookSignature(payload, header, secret) rejects any rail event whose signature doesn't check out — the boundary that stops a forged "subscription active" from ever reaching your grant logic.
A gate that holds under pressure
With the server check in place, the privileged path runs only for genuinely entitled users — no matter what the browser claims — and your webhooks only act on events Crossdeck actually sent. That's the difference between a feature flag and a security boundary.
Entitlement checked server-side with the secret key, webhook signature verified — the gate the client can't pry open.