Payment Integrations
Send receipts, invoices, subscription alerts, and trial reminders automatically when payment events fire — verified via HMAC on every request.
Each integration ships as a self-contained Cloudflare Worker in the integrations/ directory. Clone the repo, drop in your secrets, and run wrangler deploy. Every handler verifies the incoming signature before touching Mailgrid so spoofed webhooks are rejected at the door.
Stripe
Located at integrations/stripe/. The Worker validates every inbound request against Stripe's Stripe-Signature header using HMAC-SHA256 before dispatching an email.
Supported events
payment_intent.succeeded— send a payment receiptinvoice.paid— send a paid invoice confirmationinvoice.payment_failed— alert the customer to retrycustomer.subscription.created— welcome / onboarding emailcustomer.subscription.deleted— cancellation confirmationcustomer.subscription.trial_will_end— 3-day trial-ending nudge
Webhook payload → email mapping
// Verify Stripe signature const sig = request.headers.get("stripe-signature"); const valid = await verifyStripeSignature(body, sig, env.STRIPE_WEBHOOK_SECRET); if (!valid) return new Response("Unauthorized", { status: 401 }); const event = JSON.parse(body); // Map event type → email template + recipient const handlers: Record<string, (e: any) => EmailPayload> = { "payment_intent.succeeded": (e) => ({ to: e.data.object.receipt_email, subject: `Receipt — ${formatAmount(e.data.object.amount, e.data.object.currency)}`, template: "stripe-receipt", vars: { amount: formatAmount(e.data.object.amount, e.data.object.currency) }, }), "invoice.payment_failed": (e) => ({ to: e.data.object.customer_email, subject: "Action required: payment failed", template: "stripe-payment-failed", vars: { invoice_url: e.data.object.hosted_invoice_url }, }), "customer.subscription.trial_will_end": (e) => ({ to: e.data.object.customer_email, subject: "Your trial ends in 3 days", template: "stripe-trial-ending", vars: { trial_end: new Date(e.data.object.trial_end * 1000).toLocaleDateString() }, }), }; const handler = handlers[event.type]; if (!handler) return new Response("OK", { status: 200 }); // unhandled event const payload = handler(event); await sendViaMaailgrid(payload, env.MAILGRID_API_KEY, env.FROM_EMAIL);
Environment variables
| Variable | Description |
|---|---|
STRIPE_WEBHOOK_SECRET | Webhook signing secret from the Stripe Dashboard (starts whsec_) |
STRIPE_SECRET_KEY | Stripe secret key for server-side API calls (sk_live_…) |
MAILGRID_API_KEY | Your Mailgrid API key (mb_live_…) |
FROM_EMAIL | Verified sender address, e.g. billing@yourapp.com |
Deploy
# Deploy the Worker wrangler deploy --config integrations/stripe/wrangler.toml # Add secrets (prompts for value, never stored in wrangler.toml) wrangler secret put STRIPE_WEBHOOK_SECRET wrangler secret put MAILGRID_API_KEY # Local dev — forward Stripe events to your Worker stripe listen --forward-to localhost:8787
Run stripe listen --print-json to inspect raw event payloads during development before they reach your Worker.
Paddle
Located at integrations/paddle/. Paddle signs requests with a Paddle-Signature header in the format ts=…;h1=…. The Worker extracts the timestamp, recomputes HMAC-SHA256 over ts:body, and compares against h1.
Supported events
transaction.completed— payment receipt / order confirmationtransaction.payment_failed— failed charge alertsubscription.created— welcome email for new subscriberssubscription.canceled— cancellation confirmationsubscription.past_due— dunning / retry reminder
Signature verification
export async function verifyPaddleSignature( body: string, header: string, secret: string, ): Promise<boolean> { const parts = Object.fromEntries( header.split(";").map((p) => p.split("=")) ); const ts = parts["ts"]; const h1 = parts["h1"]; const key = await crypto.subtle.importKey( "raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"], ); const sig = await crypto.subtle.sign( "HMAC", key, new TextEncoder().encode(`${ts}:${body}`), ); return toHex(sig) === h1; }
Deploy
wrangler deploy --config integrations/paddle/wrangler.toml wrangler secret put PADDLE_WEBHOOK_SECRET wrangler secret put MAILGRID_API_KEY
Lemon Squeezy
Located at integrations/lemonsqueezy/. Lemon Squeezy sends an X-Signature header containing the HMAC-SHA256 hex digest of the raw request body, signed with your webhook signing secret.
Supported events
order_created— one-time purchase receiptsubscription_created— new recurring subscriber welcomesubscription_cancelled— cancellation acknowledgementsubscription_payment_failed— retry / update payment nudgelicense_key_created— deliver the customer's license key
Signature verification
export async function verifyLemonSqueezySignature( body: string, xSignature: string, secret: string, ): Promise<boolean> { const key = await crypto.subtle.importKey( "raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"], ); const sig = await crypto.subtle.sign( "HMAC", key, new TextEncoder().encode(body), ); return toHex(sig) === xSignature; }
Deploy
wrangler deploy --config integrations/lemonsqueezy/wrangler.toml wrangler secret put LEMONSQUEEZY_SIGNING_SECRET wrangler secret put MAILGRID_API_KEY
RevenueCat
Located at integrations/revenuecat/. RevenueCat webhooks use Bearer token authentication — set a shared secret in the RevenueCat dashboard and the Worker validates the Authorization: Bearer <token> header on each request. Ideal for mobile app subscriptions on iOS and Android.
Supported events
INITIAL_PURCHASE— first subscription purchase receiptRENEWAL— successful renewal confirmationCANCELLATION— subscription cancelled acknowledgementEXPIRATION— subscription expired / win-back opportunityBILLING_ISSUE— payment failed, grace period startedPRODUCT_CHANGE— plan upgrade / downgrade notification
Handler
// Bearer token auth const auth = request.headers.get("authorization") ?? ""; if (auth !== `Bearer ${env.REVENUECAT_WEBHOOK_SECRET}`) { return new Response("Unauthorized", { status: 401 }); } const { event } = await request.json(); // event.type is one of INITIAL_PURCHASE, RENEWAL, CANCELLATION, etc. if (event.type === "BILLING_ISSUE") { await sendViaMaailgrid({ to: event.app_user_id, // map to customer email via your DB subject: "Payment issue with your subscription", template: "revenuecat-billing-issue", vars: { store: event.store, product_id: event.product_id }, }, env.MAILGRID_API_KEY, env.FROM_EMAIL); }
RevenueCat's app_user_id is typically your internal user ID, not an email address. Query your database (or a KV store) to resolve it to a customer email before calling Mailgrid.
Deploy
wrangler deploy --config integrations/revenuecat/wrangler.toml wrangler secret put REVENUECAT_WEBHOOK_SECRET wrangler secret put MAILGRID_API_KEY
Chargebee
Located at integrations/chargebee/. Chargebee authenticates webhooks via HTTP Basic Auth — the username is your Chargebee site name and the password is a shared webhook password you configure in the Chargebee console.
Supported events
subscription_created— welcome email for new subscriberssubscription_cancelled— cancellation confirmation and offboardinginvoice_generated— new invoice ready notificationpayment_failed— failed charge, dunning alertpayment_succeeded— payment receiptsubscription_trial_end_reminder— trial expiry nudge
Basic Auth verification
export function verifyBasicAuth( request: Request, expectedUsername: string, expectedPassword: string, ): boolean { const authHeader = request.headers.get("authorization") ?? ""; if (!authHeader.startsWith("Basic ")) return false; const decoded = atob(authHeader.slice(6)); const [user, pass] = decoded.split(":"); return user === expectedUsername && pass === expectedPassword; }
Deploy
wrangler deploy --config integrations/chargebee/wrangler.toml wrangler secret put CHARGEBEE_WEBHOOK_USERNAME # your site name wrangler secret put CHARGEBEE_WEBHOOK_PASSWORD wrangler secret put MAILGRID_API_KEY
Webhooks quick reference
At a glance — auth method, the header to inspect, and the deploy command for each integration.
| Provider | Auth method | Signature header | Deploy command |
|---|---|---|---|
| Stripe | HMAC-SHA256 | Stripe-Signature |
wrangler deploy --config integrations/stripe/wrangler.toml |
| Paddle | HMAC-SHA256 (ts=…;h1=…) |
Paddle-Signature |
wrangler deploy --config integrations/paddle/wrangler.toml |
| Lemon Squeezy | HMAC-SHA256 | X-Signature |
wrangler deploy --config integrations/lemonsqueezy/wrangler.toml |
| RevenueCat | Bearer token | Authorization |
wrangler deploy --config integrations/revenuecat/wrangler.toml |
| Chargebee | HTTP Basic Auth | Authorization |
wrangler deploy --config integrations/chargebee/wrangler.toml |
Webhook payloads can contain PII (email addresses, billing details). Use structured logging that redacts sensitive fields, or strip them before forwarding to any log aggregator.