Send a handwritten thank-you card from a Stripe payment
When a customer pays through Stripe Checkout, mail them a card written with a real pen. One webhook handler, one API call.
What you need
- Stripe Checkout with address collection turned on, so
customer_details.addressis filled in. - A webhook endpoint subscribed to
checkout.session.completed. - A notecardHQ API key in
NOTECARDHQ_API_KEY.
The handler
import express from "express";
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
const app = express();
app.post("/webhooks/stripe", express.raw({ type: "application/json" }), async (req, res) => {
let event;
try {
event = stripe.webhooks.constructEvent(
req.body, req.headers["stripe-signature"], process.env.STRIPE_WEBHOOK_SECRET,
);
} catch (err) {
return res.status(400).send(`Webhook error: ${err.message}`);
}
if (event.type === "checkout.session.completed") {
const session = event.data.object;
const a = session.customer_details?.address;
const [firstName, ...rest] = (session.customer_details?.name ?? "").split(" ");
if (a?.line1 && firstName) {
const r = await fetch("https://you.notecardhq.com/api/v1/notes", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.NOTECARDHQ_API_KEY}`,
"Content-Type": "application/json",
// Stripe may deliver an event more than once; the same key never sends twice.
"Idempotency-Key": `checkout:${session.id}`,
},
body: JSON.stringify({
externalId: session.customer ?? session.id,
recipient: {
firstName, lastName: rest.join(" "),
address1: a.line1, address2: a.line2 ?? "",
city: a.city, state: a.state ?? "", postalCode: a.postal_code ?? "",
country: a.country,
},
content: `Hi ${firstName},\n\nThank you for your order. It means a lot to a small team.\n\nThe team`,
context: { stripe_session: session.id },
}),
});
// Ask Stripe to retry only on temporary failures. 409 and 422 need a person.
if (r.status === 429 || r.status >= 500) return res.status(503).end();
if (!r.ok) console.error("notecardHQ", r.status, await r.text());
}
}
res.json({ received: true });
});
app.listen(3000);
Why it is safe to retry
Stripe retries webhooks that fail, and can deliver the same event twice. Because the Idempotency-Key is built from the Checkout Session id, every delivery of that event maps to the same note. The first request creates it; every repeat returns it.
Only first orders
To thank a customer once, not on every purchase, key the note on the customer instead: first-order:${session.customer}. Later payments by the same customer reuse the key, and notecardHQ answers with the original note (or 409 if the body changed), so no second card goes out.
Field rules, statuses and errors are in the API reference. Prefer another language? See the quickstart.
Send your first note.
Tell us what you want to send and we will set up your workspace.