Pro & Payments
Webhooks
The Creem webhook handler in the buyer portal.
The portal's Creem webhook handler lives at app/api/payments/creem/webhook/route.ts. It is responsible for:
- Verifying the HMAC-SHA256 signature using
CREEM_WEBHOOK_SECRET. - Reading the event type (
checkout.completed,subscription.paid, etc.). - Routing by
metadata.kind:subscription→ update the user'ssubscriptionandcredit_ledgerrows.one_time(credit pack) → update the user'screditsandcredit_ledgerrows.pro→ mark thepro_licenserowactiveand invite the user as a GitHub collaborator.
Signature verification
Creem signs each webhook with HMAC-SHA256 of the raw body using your CREEM_WEBHOOK_SECRET. The signature is in the creem-signature header. The handler uses a timing-safe comparison to prevent timing attacks.
Idempotency
The handler is idempotent on the event id and the provider_payment_id. Replaying the same webhook is safe.
Setting up a webhook in the Creem dashboard
- Sign in to Creem.
- Create a new product for AI Cost Gate Pro (or reuse the placeholder
prod_pro_ai_cost_gate). - Under "Webhooks", add an endpoint pointing to
https://aicostgate.com/api/payments/creem/webhook. - Copy the signing secret into
CREEM_WEBHOOK_SECRETin your.env.local.
Local testing
You can replay a signed webhook with curl:
BODY='{"eventType":"checkout.completed","id":"evt_test","object":{...}}'
SIG=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "$CREEM_WEBHOOK_SECRET" -hex | awk '{print $2}')
curl -X POST https://localhost:6080/api/payments/creem/webhook \
-H "Content-Type: application/json" \
-H "creem-signature: $SIG" \
--data "$BODY"