Le besoin
Une agence de formation cliente nous a demandé : "Quand un élève paie via Stripe, je veux automatiquement une fiche Notion créée dans la base 'Élèves', un email envoyé avec un lien Calendly, et l'élève ajouté à la mailing list Klaviyo. Manuellement, ça nous prend 10 minutes par client."
Volume : 30-50 paiements / mois. Donc 5-8 heures de manipulation manuelle par mois, à 35 €/h, ça fait 200-300 €/mois "gaspillés".
On a livré la solution en 1 jour. Voici le code et les pièges qu'on a évités.
L'architecture
Stripe ──webhook──> Cloudflare Worker ──> Notion API
──> Resend (email Calendly)
──> Klaviyo API (subscribe)
Pourquoi Cloudflare Workers et pas Vercel/Lambda :
- Cold start sub-50ms (Stripe attend < 5 s, sinon retry).
- Idempotency naturelle via KV.
- Coût : 5 $/mois pour 10 M req. Gratuit en dessous de 100 k req/mois.
Le code complet (50 lignes)
// worker.ts
import Stripe from "stripe";
import { Client as NotionClient } from "@notionhq/client";
interface Env {
STRIPE_KEY: string;
STRIPE_WEBHOOK_SECRET: string;
NOTION_KEY: string;
NOTION_DB_ID: string;
RESEND_KEY: string;
KLAVIYO_KEY: string;
EVENTS_KV: KVNamespace; // Idempotency
}
export default {
async fetch(req: Request, env: Env): Promise<Response> {
const sig = req.headers.get("stripe-signature");
const body = await req.text();
const stripe = new Stripe(env.STRIPE_KEY);
// 1. Vérification signature
const event = await stripe.webhooks.constructEventAsync(
body,
sig!,
env.STRIPE_WEBHOOK_SECRET,
);
// 2. Idempotency : on stocke l'event.id dans KV pour dédupe
const seen = await env.EVENTS_KV.get(event.id);
if (seen) return new Response("ok (dupe)", { status: 200 });
await env.EVENTS_KV.put(event.id, "1", { expirationTtl: 86400 });
// 3. On ne traite que checkout.session.completed
if (event.type !== "checkout.session.completed") {
return new Response("ok (ignored)", { status: 200 });
}
const session = event.data.object as Stripe.Checkout.Session;
const email = session.customer_details?.email;
const name = session.customer_details?.name;
if (!email) return new Response("ok (no email)", { status: 200 });
// 4. Création Notion + email Calendly + Klaviyo en parallèle
await Promise.all([
createNotionEntry(env, { email, name, sessionId: session.id }),
sendCalendlyEmail(env, { email, name }),
addToKlaviyo(env, { email, name }),
]);
return new Response("ok", { status: 200 });
},
};
Les 3 helpers (15 lignes)
async function createNotionEntry(env: Env, p: { email; name; sessionId }) {
const notion = new NotionClient({ auth: env.NOTION_KEY });
await notion.pages.create({
parent: { database_id: env.NOTION_DB_ID },
properties: {
Name: { title: [{ text: { content: p.name ?? p.email } }] },
Email: { email: p.email },
Stripe: { url: `https://dashboard.stripe.com/payments/${p.sessionId}` },
Status: { select: { name: "À onboarder" } },
},
});
}
async function sendCalendlyEmail(env: Env, p: { email; name }) {
await fetch("https://api.resend.com/emails", {
method: "POST",
headers: { Authorization: `Bearer ${env.RESEND_KEY}` },
body: JSON.stringify({
from: "[email protected]",
to: p.email,
subject: "Bienvenue ! Réservez votre session de cadrage",
html: `Bonjour ${p.name ?? ""}, réservez votre session : <a href="https://cal.com/ecole/onboarding">Calendly</a>`,
}),
});
}
async function addToKlaviyo(env: Env, p: { email; name }) {
await fetch("https://a.klaviyo.com/api/profiles/", {
method: "POST",
headers: {
Authorization: `Klaviyo-API-Key ${env.KLAVIYO_KEY}`,
"Content-Type": "application/json",
revision: "2024-10-15",
},
body: JSON.stringify({
data: {
type: "profile",
attributes: { email: p.email, first_name: p.name },
},
}),
});
}
50 lignes (tout compris). Déployable immédiatement sur Cloudflare Workers.
Les 3 pièges qu'on a évités
1. Webhook double-fire
Stripe peut envoyer le même event 2-3 fois (réseau, retry). Sans déduplication, vous créez 3 fiches Notion. Le KV avec event.id règle ça en 2 lignes.
2. Timeout Stripe
Stripe attend 5 secondes max pour le 200. Sur un cold start AWS Lambda, c'était souvent 6-7 secondes. D'où Cloudflare Workers qui démarre instantanément.
3. Échec partiel
Si Notion crashe mais Klaviyo passe, vous voulez retry uniquement Notion. Solution : chaque sous-task est dans un try/catch, et les échecs sont loggés dans une queue (Cloudflare Queues) pour retry async. Pas critique pour ce client (volume faible), mais on l'a documenté pour la V2.
Variantes possibles
- Stripe → Airtable au lieu de Notion : changer juste le helper.
- Stripe → Slack pour notif équipe : ajouter un 4e helper, 8 lignes.
- Stripe → HubSpot pour lead score : remplacer Klaviyo.
Le pattern reste identique : Worker reçoit, dédupe, dispatch vers N outils en parallèle.
Le coût total
- Cloudflare Workers : 0 € (sous le free tier)
- KV : 0 € (sous le free tier)
- Resend : 0 € (3 000 emails/mois gratuits)
Total : 0 €/mois. Pour automatiser 5-8 heures de manuel.
Si vous avez un workflow similaire à automatiser (Stripe vers n'importe quel outil), parlez-nous. C'est typiquement le genre de mission qu'on livre en 1 jour, code source dans votre repo.






