Skip to content
DocsPurchase thanks

Examples

Purchase thanks

Official Kody doc

The homepage Trigger it card labeled Event is this example. A purchase is a moment your factory can already know about. The package that reacts does not need a model in the loop and does not need you to paste the receipt into chat.

Live public package: @kentcdodds/stripe-purchase-thanks. After a verified successful Stripe checkout it indexes Kent's business Gmail for the buyer, creates a thank-you draft on a relevant thread (To is the buyer, never Kent), and posts a one-liner that the draft is ready. Draft only. Never send.

The homepage calls this an event because the interesting fact is the purchase, not the HTTP door. Kent's public package uses a Stripe webhook as the way that fact arrives, then treats the verified checkout as the event that starts the thank-you. You can also emit @you/purchase.completed from one package and subscribe from another.

Subscriptions and package-emitted topics: Subscriptions and events. Vendor POST ingress: Inbound webhooks. Drafts the agent must not send: Gmail drafts without send.

Two honest shapes

One package, vendor ingress. Stripe POSTs checkout.session.completed. The handler fetch-back verifies the event id (the body is untrusted; only id is used), keeps successful paid checkouts, and calls thankPurchase. That is Kent's public package.

import handleStripeWebhook from 'kody:@you/stripe-purchase-thanks/handle-stripe-webhook'

export default async function main() {
	return await handleStripeWebhook({
		request: { json: { id: 'evt_1ABC', type: 'checkout.session.completed' } },
	})
}

Two packages, an owned topic. The Stripe (or commerce) package verifies the payment, then emits. The thanks package subscribes. Other packages can listen without sharing the webhook secret.

import { events } from 'kody:runtime'

await events.dispatch({
	topic: '@you/purchase.completed',
	idempotencyKey: `stripe:checkout:${eventId}`,
	payload: {
		eventId,
		product: 'Kody Standard',
		customerEmail: 'ada@example.com',
	},
})
{
	"name": "@you/purchase-thanks",
	"kody": {
		"subscriptions": {
			"@you/purchase.completed": {
				"handler": "./src/on-purchase-completed.ts",
				"description": "Draft a thank-you after a verified purchase."
			}
		}
	}
}

events.dispatch is unavailable in ad hoc execute. Emit from package code (or a static import of an export that dispatches). Payloads are JSON objects capped at 64 KiB. Store Gmail threads and draft ids in packageStorage() and emit a reference when the thank-you record is large.

Package shape

Kent's thank-you export is the part you replay from chat after a payment is already verified:

import { thankPurchase } from 'kody:@you/stripe-purchase-thanks/thank-purchase'

export default async function main() {
	return await thankPurchase({
		eventId: 'evt_1ABC',
		amount: 1200,
		currency: 'usd',
		product: 'Kody Standard',
		customerName: 'Ada',
		customerEmail: 'ada@example.com',
		livemode: true,
	})
}

Rules that keep this safe:

  • Fetch-back verify. Do not trust the webhook JSON beyond the event id.
  • Draft. Do not send. If the Google token can send, lock the published package and the integration so execute cannot widen it. See Gmail drafts without send.
  • Money alerts stay out of this package. Kent keeps those in @kentcdodds/stripe-alerts.
  • Never log customer email, checkout session ids, or receipt URLs in public channels.
  • After publish, smoke-test the webhook export with a dry-run payload, or replay thankPurchase on facts you already verified. For the owned-topic shape, use packageSubscriptionDispatch.

Example prompts

Build the event shape

Search Kody for purchase thanks and package-emitted events. I want @me/purchase.completed emitted after a verified paid checkout, and a second package that drafts a thank-you (never send) when that event lands. Show me the kody.emits and kody.subscriptions snippets, then persist both packages. Smoke-test the subscriber with packageSubscriptionDispatch.

Fork the public one

Open https://kody.codes/@kentcdodds/stripe-purchase-thanks, fork it, and point the Stripe webhook at my checkout. Keep drafts-only. Do not send. Do not disable Kent's live stripe webhook.

Replay one purchase

Import thankPurchase from my fork and replay event evt_… that I already verified. Show me which Gmail thread it picked and the draft id. Do not send the draft.

What you see

SurfaceWhat it is for
Stripe dashboard → webhooksThe minted Kody URL (copied from package settings, never from chat)
Package settings → WebhooksMint / reveal / rotate for the stripe door
Gmail DraftsThe thank-you, To = buyer, waiting on a human to send
Discord (Kent's copy)One-liner that the draft is ready
/account/activityWebhook deliveries and subscription runs
Chat (execute)Replay thankPurchase or dry-run the webhook handler

The homepage card is a tile with the kicker Event and the title Purchase thanks. It links here.

Where to go next

Working with an agent? This page is also plain markdown at /docs/purchase-thanks.md, or load it over MCP with search({ entity: 'guide:purchase_thanks' }).