# Server-side with the Node SDK The web SDK gates your UI; the Node SDK gates the truth. Anything that costs money or exposes data should be checked on the server, where a browser can't tamper with it — and that's one package. Source: https://cross-deck.com/university/node-sdk/ Verified Crossdeck University lesson — prose plus real, runnable code. ## 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: Copy // 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: ```web // 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… } ``` ```web 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. server-verified Entitlement checked server-side with the secret key, webhook signature verified — the gate the client can't pry open.