Connect Stripe
Wire Stripe as a verified payment rail. Crossdeck signature-verifies inbound webhooks, reconciles against the Stripe API, and projects subscriptions, refunds, and disputes into the unified revenue ledger — across the same customer record as your App Store and Google Play subscriptions.
Verified Stripe events flowing into your dashboard within ~2 seconds of delivery, automatic signature checks, idempotent processing, and continuous reconciliation against Stripe's API to repair any missed webhooks.
Before you start
You'll need:
- A Crossdeck project with the web platform enabled (or a hybrid-model project)
- A Stripe account (test mode is fine for first-time setup)
- Owner or developer permissions in the Stripe Dashboard
Step 1 · Find your Stripe Account ID
Crossdeck stores your Account ID alongside your project so it can attribute incoming webhooks to the correct rail.
Go to dashboard.stripe.com/settings/account, or click the account name in the top-right of the Stripe Dashboard → Account info.
It starts with acct_ — for example acct_1NxYz123456789. Same ID across test and live mode.
Enter the email associated with the account too — Crossdeck uses it for rail-related notifications (webhook delivery issues, billing-retry summaries, reconciliation drift).
Step 2 · Add the webhook endpoint
Crossdeck issues a unique endpoint URL per project. Webhooks delivered to that URL are signature-verified before they touch your ledger.
dashboard.stripe.com/webhooks · click Add an endpoint.
Crossdeck displays this on the Connect Stripe step — it looks like https://api.cross-deck.com/v1/webhooks/stripe/proj_xxx. Each project has its own endpoint, so events are routed correctly.
Toggle "Listen on the latest API version". This keeps event payloads consistent with what Crossdeck expects.
Stripe only delivers events you explicitly subscribe to. The default selection is empty — if you save the endpoint without ticking events, Crossdeck will never receive anything.
Step 3 · Select the events Crossdeck needs
Click "Select events" in the endpoint setup and tick the nine events below. Crossdeck needs all of them to maintain the ledger correctly — missing any one creates a gap that the reconciliation worker can't always close.
| Event | Why Crossdeck needs it |
|---|---|
checkout.session.completed |
New checkout completed — opens a customer record and provisional entitlement. |
customer.subscription.created |
Subscription started — moves the customer to TRIAL or ACTIVE. |
customer.subscription.updated |
Plan change, billing cycle change, status change — all subscription transitions. |
customer.subscription.deleted |
Subscription cancelled — moves the customer to EXPIRED at period end. |
invoice.payment_succeeded |
Renewal succeeded — extends the entitlement period. |
invoice.payment_failed |
Renewal failed — moves the customer to BILLING_RETRY. Access stays granted. |
payment_intent.payment_failed |
One-off payment failure — surfaces in Operations queue. |
charge.refunded |
Refund issued — revokes the matching entitlement. |
charge.dispute.created |
Dispute opened — flags the customer in the at-risk view. |
The Connect Stripe onboarding step has a Copy all events button — paste the list straight into Stripe's event picker search box and tick them in one pass instead of scrolling.
Step 4 · Verify the connection
Save the endpoint in Stripe. Crossdeck will mark this step verified once it has:
- Received at least one signed webhook from Stripe
- Successfully verified the signature against your endpoint's signing secret
- Confirmed the Account ID in the event matches what you entered
To trigger a quick verification event, run any of the following in test mode:
# Trigger a fake checkout.session.completed event
stripe trigger checkout.session.completed
# Or send a payment_succeeded
stripe trigger invoice.payment_succeeded
Or do a real test purchase using a test card through your live checkout flow. Either way, the Connect Stripe step in your onboarding should flip to verified within a few seconds.
How webhook signing works
Crossdeck never trusts an unverified webhook. Every inbound delivery is checked against the signing secret Stripe generated when you saved the endpoint:
- Stripe sends a
Stripe-Signatureheader with each webhook - Crossdeck retrieves the signing secret from Google Cloud Secret Manager (it never lives in Firestore or in your app)
- The HMAC-SHA256 signature is verified against the raw request body
- Any failure is logged to your audit trail and the event is dropped — no state mutation, no entitlement change
Even after signature verification passes, Crossdeck calls Stripe back via the API to confirm the event before mutating state. Webhooks tell us something happened; the API call confirms what actually exists.
Troubleshooting
No events appearing in Crossdeck
- Open Stripe → Webhooks → your endpoint → Recent events. If Stripe shows no deliveries, you haven't triggered any matching events yet (try the CLI command above).
- If Stripe shows deliveries but Crossdeck shows none, check the Response column in Stripe. A 4xx response means signature verification failed — your endpoint URL probably points to a different project than the one you connected.
- Check the audit log in your Crossdeck dashboard. Failed signature checks are logged with the reason.
Signature verification keeps failing
- Make sure you're using the endpoint URL Crossdeck issued for this project. Cross-project URLs will fail verification because each project has a distinct signing secret.
- If you rotated the signing secret in Stripe, re-trigger an event after Crossdeck syncs (within ~60 seconds).
I'm seeing duplicate subscription state changes
- Stripe occasionally re-delivers events. Crossdeck is idempotent on
event.id— duplicates are ignored at the database level. - If you see multiple state changes in your audit log, they came from genuinely different events (e.g. update → cancel → reactivate). The audit log shows the full evidence chain.
Crossdeck is asking for the webhook secret manually
- For most accounts, Crossdeck retrieves the signing secret automatically when you save the endpoint via Stripe Connect (P1).
- If you set up the endpoint manually, paste the signing secret in the dashboard under Settings → Payment rails → Stripe → Webhook secret. It's stored in Secret Manager — never in Firestore, never in your app.
Reference
Endpoint URL format
Crossdeck issues one endpoint URL per project, distinct for production and sandbox:
# Production
https://api.cross-deck.com/v1/webhooks/stripe/proj_xxx
# Sandbox (only delivered Stripe test-mode events)
https://api.cross-deck.com/v1/webhooks/stripe/proj_xxx?env=sandbox
What Crossdeck does per event
| Event | State transition | Default access |
|---|---|---|
customer.subscription.created (trial) | → TRIAL | Granted |
customer.subscription.created (active) | → ACTIVE | Granted |
invoice.payment_succeeded | → ACTIVE | Granted |
invoice.payment_failed | → BILLING_RETRY | Granted (default) |
customer.subscription.updated (canceled, period not ended) | → ACTIVE until period end | Granted until expiry |
customer.subscription.deleted | → EXPIRED | Revoked |
charge.refunded | → REFUNDED | Revoked immediately |
charge.dispute.created | (no state change — flagged) | Granted, surfaced in operations |
What's next
With Stripe wired:
- Map your Stripe products to entitlements — connect
price_xxxidentifiers to access concepts your app checks for - Install the web SDK to call
isEntitled("pro")from your browser code - Connect App Store — same customer record, same entitlement, cross-platform truth