A Stripe webhook fires twice. Your customer still gets one receipt.

Stripe retries a webhook until your endpoint returns 200, so a naive receipt job can email the customer twice. Key the NoticeAPI send on the Stripe event id, and those retries collapse into one message you can trace from accepted to delivered.

Shortest working path
Send one receipt per Stripe event
import { NoticeAPI } from "noticeapi";

const notice = new NoticeAPI(process.env.NOTICEAPI_API_KEY!);

export async function sendReceiptFromStripeEvent(event: {
  id: string;
  type: string;
  data: { object: { customer_email?: string; number?: string } };
}) {
  const invoice = event.data.object;
  if (!invoice.customer_email) return;

  return notice.emails.send(
    {
      from: "Acme Billing <[email protected]>",
      to: invoice.customer_email,
      subject: `Invoice ${invoice.number ?? "receipt"}`,
      html: "<p>Your billing receipt is ready.</p>",
    },
    { idempotencyKey: event.id }, // Stripe retries reuse the id -> one email
  );
}

Connect it, test it, then move traffic.

  1. 1

    Verify the Stripe signature

    Check the Stripe-Signature header in your handler before it decides to send anything. The webhook hits your app, not NoticeAPI.

  2. 2

    Map the event to a message

    Route invoice.paid, invoice.payment_failed, and subscription events to the template and recipient each one deserves.

  3. 3

    Send keyed on the event id

    Call notice.emails.send with event.id as the retry key so Stripe's retries cannot stack up duplicate receipts.

  4. 4

    Trace it when a customer asks

    Search the stored message and recipient timeline, or replay the delivery from a signed webhook, when someone says the receipt never came.

What remains visible after integration.

One receipt per event id

Use event.id, or a stable invoice or payment id, as the retry key. A repeated webhook returns the first send instead of sending another email.

Receipts, invoices, failed payments

Render each billing notice from a stored template. Attach the invoice PDF through the REST send API on a paid plan.

Rehearse both paths first

Fire the success and failure sends at simulator recipients from a sandbox sender before a real card is ever charged.

Know if the receipt landed

Signed webhooks report delivered, bounced, or suppressed, so a missing receipt surfaces in your systems instead of a support ticket.

Receipts are transactional. Keep the marketing separate.

A Stripe-triggered receipt, invoice, or payment failure is transactional email your customer expects. Send product news, offers, and newsletters through audience broadcasts, where one-click unsubscribe and List-Unsubscribe are handled for you.

Read the implementation.

Questions before you ship.

Does NoticeAPI replace Stripe Billing?

No. Stripe stays your billing system. NoticeAPI sends the email your product decides to send once it has verified a Stripe event.

Which Stripe id should I reuse?

The Stripe event id covers webhook retries. For a job built around one object, a stable invoice or payment id works too.

Can I attach the invoice PDF?

Yes, on a paid plan. Pass the PDF bytes to the REST send API's attachment field once your app has generated them.

Test it. Then send for real.

Simulator outcomes are free. Move to a verified domain when the path is ready.

Create your account Compare plans