Webhook Processing Example
A resilient way to process incoming webhooks from external providers like Stripe, GitHub, or Shopify.
The Scenario
- Parse & Validate: Ensure the webhook is authentic and well-formed.
- Idempotency Check: Ensure we don't process the same webhook ID twice.
- Core Logic: Update our local state based on the webhook payload.
- Sync Downstream: Notify other internal services.
The Code
ts
import { create, createIdempotencyStore } from "@eddiecbrl/orchestrix";
const store = createIdempotencyStore(); // Use Redis in production!
const webhookFlow = create("stripe-webhook", { idempotency: store })
.step("validate-signature", (ctx) => {
const { payload, signature } = ctx.input;
const isValid = stripe.verify(payload, signature);
if (!isValid) throw new Error("Invalid signature");
})
.step("update-subscription", async (ctx) => {
const { data } = ctx.input;
await db.subscriptions.update(data.customer, { status: data.status });
}, {
retries: 3,
timeoutMs: 2000
})
.step("notify-crm", async (ctx) => {
await crmService.updateContact(ctx.input.data.customer, { lastBillingStatus: 'ok' });
});
// Express/Fastify handler
app.post('/webhooks/stripe', async (req, res) => {
const stripeEventId = req.body.id;
const result = await webhookFlow.run(req.body, {
key: `webhook:${stripeEventId}`,
ttlMs: 24 * 60 * 60 * 1000 // Keep for 24 hours
});
if (result.status === 'completed') {
res.status(200).send("Processed");
} else {
res.status(500).send("Error");
}
});Highlights
- Native Idempotency: Webhook providers often send the same event multiple times. Orchestrix's idempotency handles this automatically.
- Fail-Safe: If
notify-crmfails, the webhook has already updated our core subscription state. We can use retries to makenotify-crmmore reliable. - Observability: Using hooks, you can track how many webhooks are being retried or failing, providing early warning for integration issues.