5 min Quickstart
TL;DR for experienced engineers. Detect Sumo purchases, create users, provision access.
How it works
- 1. Sumo creates checkout sessions on your connected Stripe account
- 2. You receive standard
checkout.session.completedwebhooks - 3. Sumo purchases have
sumo_campaign_idin metadata — that's your detection flag - 4. Create the user account, link subscription, provision access
Metadata Fields
| Field | Use |
|---|---|
| sumo_campaign_id | Detection flag — if present, it's a Sumo purchase |
| sumo_product_tier_id | Which tier was purchased — use for access levels |
| sumo_quantity | Number of licenses (default: 1) |
| sumo_product_id | Sumo product ID |
| sumo_user_id | Sumo buyer ID (optional tracking) |
Node.js / Express
JavaScript
// Express/Node.js - minimal Sumo webhook handler
app.post('/webhooks/stripe', express.raw({type: 'application/json'}), async (req, res) => {
const event = stripe.webhooks.constructEvent(
req.body,
req.headers['stripe-signature'],
process.env.STRIPE_WEBHOOK_SECRET
);
if (event.type === 'checkout.session.completed') {
const session = event.data.object;
// Sumo purchase = has sumo_campaign_id in metadata
if (session.metadata?.sumo_campaign_id) {
const user = await db.users.upsert({
where: { email: session.customer_details.email },
create: {
email: session.customer_details.email,
stripeCustomerId: session.customer,
source: 'sumo'
}
});
await db.subscriptions.create({
userId: user.id,
stripeSubscriptionId: session.subscription,
tierId: session.metadata.sumo_product_tier_id,
quantity: parseInt(session.metadata.sumo_quantity) || 1
});
await provisionAccess(user.id, session.metadata.sumo_product_tier_id);
}
}
res.json({ received: true });
}); Python / FastAPI
Python
# FastAPI - minimal Sumo webhook handler
@app.post("/webhooks/stripe")
async def webhook(request: Request):
event = stripe.Webhook.construct_event(
await request.body(),
request.headers.get("stripe-signature"),
WEBHOOK_SECRET
)
if event["type"] == "checkout.session.completed":
session = event["data"]["object"]
# Sumo purchase = has sumo_campaign_id in metadata
if session.get("metadata", {}).get("sumo_campaign_id"):
user = await find_or_create_user(
email=session["customer_details"]["email"],
stripe_customer_id=session["customer"]
)
await create_subscription(
user_id=user.id,
stripe_sub_id=session["subscription"],
tier_id=session["metadata"]["sumo_product_tier_id"],
quantity=int(session["metadata"].get("sumo_quantity", 1))
)
await provision_access(user.id, session["metadata"]["sumo_product_tier_id"])
return {"status": "ok"} Test Locally
Bash
# Local testing with Stripe CLI
stripe listen --forward-to localhost:8000/webhooks/stripe
# Create test checkout with Sumo metadata
stripe checkout sessions create \
--success-url="http://localhost:3000/success" \
--mode=subscription \
--line-items[0][price]=price_xxx \
--line-items[0][quantity]=1 \
--metadata[sumo_campaign_id]=test_camp \
--metadata[sumo_product_tier_id]=test_tier \
--metadata[sumo_quantity]=1 Quick Checklist
Key Points
Idempotency is critical
Stripe may retry webhooks. Use upsert/findOrCreate patterns. Add unique constraint on stripe_subscription_id.
User might already exist
Check by email first. If they exist, just link the new subscription. Don't create duplicates.
Return 200 fast
Return success quickly, queue heavy work (emails, complex provisioning) for background processing.