Skip to content

Entitlements

Entitlements are the access records your app owns after a payer completes delegated checkout: orders, memberships, subscriptions, credits, seats, downloads, or anything else your product unlocks.

A merchant Proxy webhook endpoint is not required to create a handoff or open a payment-level checkout. Add one when your app needs Proxy events to grant or update access, especially for production fulfillment and subscriptions.

Create a webhook endpoint in Proxy that points to your backend:

https://app.example.com/api/proxy/webhook

Save the endpoint signing secret as PROXY_WEBHOOK_SIGNING_SECRET.

For one-time purchases, subscribe to:

  • proxy_session.paid
  • proxy_session.provisionable
  • proxy_session.cancelled
  • proxy_session.failed
  • proxy_session.expired

For subscriptions, also subscribe to:

  • subscription.renewed
  • subscription.payment_failed
  • subscription.cancel_scheduled
  • subscription.cancelled

Use the server SDK to verify the webhook and resolve the latest Proxy state before updating access.

import {
createProxyCheckoutServerClient,
type ResolvedProxyEvent,
} from "@proxy-checkout/server-js";
const proxy = createProxyCheckoutServerClient({
apiKey: process.env.PROXY_SECRET_KEY!,
});
// POST /api/proxy/webhook
export async function POST(request: Request) {
return proxy.webhooks.handle(request, {
secret: process.env.PROXY_WEBHOOK_SIGNING_SECRET!,
async onResolved(resolved) {
await updateEntitlements(resolved);
},
});
}
async function updateEntitlements(resolved: ResolvedProxyEvent) {
switch (resolved.kind) {
case "initial_provision":
// Grant access for resolved.session.buyerReference.
// Key the write by resolved.session.id so repeated deliveries are safe.
return;
case "subscription_renewed":
// Extend or confirm subscription access.
return;
case "subscription_cancel_scheduled":
// Keep access through the current period and mark non-renewing.
return;
case "subscription_cancelled":
// End access according to your product policy.
return;
case "payment_risk":
// Apply your dunning or restricted-access policy.
return;
case "terminal_session":
// Mark the delegated purchase failed, expired, or unavailable.
return;
case "ignored":
case "payment_attempt":
return;
}
}

Do not grant access from raw webhook payload fields alone. The handler verifies the signature and re-reads current Proxy state before calling onResolved.

Webhook deliveries can repeat. Make entitlement writes idempotent by keying them to your own access record plus the Proxy object that caused the update:

  • one-time access: resolved.session.id
  • subscription access: resolved.subscription.id
  • buyer-owned access: resolved.session.buyerReference

For a Proxy-mediated delegated purchase, do not also grant access from a direct Stripe webhook path for the same order or subscription. Stripe should send payment evidence to Proxy, and Proxy should send normalized, signed events to your backend.