Some payments finish later, not during the request: the customer confirms in their bank's app a few minutes after checkout, or pays by bank transfer. Your shop can't wait for that. Instead, the provider calls your shop when it happens: it sends a POST to an address you gave it. That address is a webhook. For once, your shop is the server and the provider is the client.
Events: the provider sends an event as JSON: what happened in "type", and the payment it happened to in "data", an object inside the object. express.raw puts the body in req.body exactly as it was sent, JSON.parse turns that JSON text into an object, and as tells TypeScript its shape, which JSON.parse can't know. The same idea for a mail service that tells you an email bounced:
type MailEvent = {
type: string;
data: { to: string };
};
const bounced: string[] = [];
app.post("/webhooks/mail", express.raw({ type: "application/json" }), (req: Request, res: Response) => {
const event = JSON.parse(req.body) as MailEvent;
if (event.type === "email.bounced") {
bounced.push(event.data.to);
}
res.json({ received: true });
});
Answer {"received": true} to every event, even the ones you don't act on, like payment.failed: a provider resends an event until it gets a 2xx answer. The path has no id in it, so the order's id comes from the event, in "data":
GET /orders/10 → 200 {…, "payment_id": null, "status": "pending", "total": 21}
POST /webhooks/payments {"type": "payment.failed", "data": {"id": "pay_7", "amount": 21, "order_id": 10}}
→ 200 {"received": true}
GET /orders/10 → 200 {…, "payment_id": null, "status": "pending", "total": 21}
POST /webhooks/payments {"type": "payment.succeeded", "data": {"id": "pay_8", "amount": 21, "order_id": 10}}
→ 200 {"received": true}
GET /orders/10 → 200 {…, "payment_id": "pay_8", "status": "paid", "total": 21}
Your task: add POST /webhooks/payments, with express.raw({ type: "application/json" }) between the path and the function. Read the event with JSON.parse and as PaymentEvent, and when its type is "payment.succeeded", mark the order paid with the payment's id. Answer { received: true } for every event.
Assignment
Follow the Coding Tutorial to build the webhook POST /webhooks/payments.
Hint
The payment is event.data, a Payment. Use the same UPDATE as in the pay route, run with payment.id and payment.order_id, in the order of the ? marks.
Free plan: 30 tutor messages. PRO is unlimited.
