Skip to content

Start typing to search the documentation.

Stripe

AI-generated, awaiting review View as Markdown @flue/stripe

Quickstart

Add verified webhook ingress and application-owned API behavior to an existing Flue project with the Stripe blueprint. Run the following command in your terminal or coding agent of choice:

flue add channel stripe

Overview

The blueprint installs @flue/stripe, Stripe’s official stripe SDK, and its required TypeScript peer when needed. It creates <source-root>/channels/stripe.ts, where the named channel export verifies snapshot webhook events by default and the project-owned client handles outbound API calls. Adapt the selected events, agent, and tool to the application.

import Stripe from 'stripe';
import { createStripeChannel } from '@flue/stripe';
import { dispatch } from '@flue/runtime';
import billing from '../agents/billing.ts';

export const client = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  httpClient: Stripe.createFetchHttpClient(),
});

export const channel = createStripeChannel({
  client,
  webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
  async webhook({ event }) {
    if (event.type !== 'checkout.session.completed') return;
    const session = event.data.object;
    const customerId =
      typeof session.customer === 'string' ? session.customer : session.customer?.id;
    if (!customerId) return;

    await dispatch(billing, {
      id: customerId,
      input: { type: `stripe.${event.type}`, eventId: event.id },
    });
  },
});

A matching event is admitted to the billing agent identified by its Stripe customer. Other events receive an empty successful response. The generated module also defines a customer-bound retrieval tool; the blueprint wires that tool into the billing agent. For Cloudflare targets, the same SDK uses its Fetch and Web Crypto implementation.

Configure

VariablePurpose
STRIPE_WEBHOOK_SECRETRequired — Verifies inbound deliveries.
STRIPE_SECRET_KEYRequired — Authenticates outbound SDK calls.

It installs @flue/stripe and Stripe’s official stripe SDK. The SDK verifies inbound payloads and remains the project-owned client for outbound API calls. The blueprint creates src/channels/stripe.ts with named channel and client exports.

Configure the Stripe event destination as:

https://example.com/channels/stripe/webhook

If flue() is mounted beneath an outer prefix, include that prefix. Subscribe only to event types the application handles. Keep both credentials in the project’s existing secret system.

Channel module

Snapshot events are the default:

import Stripe from 'stripe';
import { createStripeChannel } from '@flue/stripe';
import { defineTool, dispatch } from '@flue/runtime';
import billing from '../agents/billing.ts';

export const client = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  httpClient: Stripe.createFetchHttpClient(),
});

export const channel = createStripeChannel({
  client,
  webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,

  // Path: /channels/stripe/webhook
  async webhook({ event }) {
    switch (event.type) {
      case 'checkout.session.completed':
      case 'checkout.session.async_payment_succeeded': {
        const session = event.data.object;
        const customerId =
          typeof session.customer === 'string' ? session.customer : session.customer?.id;
        if (!customerId) return;

        await dispatch(billing, {
          id: customerId,
          input: {
            type: `stripe.${event.type}`,
            eventId: event.id,
            checkoutSessionId: session.id,
          },
        });
        return;
      }
      default:
        return;
    }
  },
});

export function retrieveCustomer(customerId: string) {
  return defineTool({
    name: 'retrieve_stripe_customer',
    description: 'Retrieve the Stripe customer bound to this billing agent.',
    parameters: {
      type: 'object',
      properties: {},
      additionalProperties: false,
    },
    async execute() {
      const customer = await client.customers.retrieve(customerId);
      return JSON.stringify(
        'deleted' in customer
          ? { id: customer.id, deleted: true }
          : { id: customer.id, name: customer.name, email: customer.email },
      );
    },
  });
}

@flue/stripe gives Stripe’s SDK the exact request bytes and Stripe-Signature header before invoking webhook. Returning nothing produces an empty 200. A JSON-compatible value becomes the response body, and a normal Hono or Fetch Response passes through unchanged.

The example uses a customer id as the agent instance id for one Stripe account. Stripe has no universal conversation identity. Choose the customer, subscription, account, Checkout Session, or other resource that represents the application’s unit of work. Connect and organization destinations should include the verified event.account or event.context when their resource namespaces require it.

Bind the tool

import { createAgent } from '@flue/runtime';
import { retrieveCustomer } from '../channels/stripe.ts';

export default createAgent(({ id: customerId }) => ({
  model: 'anthropic/claude-haiku-4-5',
  tools: [retrieveCustomer(customerId)],
}));

The model can invoke the lookup but cannot select another customer, account, or credential. Trusted application code binds those values. The channel-agent import cycle is supported because both imported bindings are read only inside deferred callbacks or initializers.

Thin event notifications

Set eventPayload: 'thin' only for an event destination configured to send Stripe thin event notifications:

export const channel = createStripeChannel({
  client,
  webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
  eventPayload: 'thin',

  // Path: /channels/stripe/webhook
  async webhook({ event }) {
    const relatedObject = await event.fetchRelatedObject();
    await handleRelatedObject(event, relatedObject);
  },
});

The callback receives Stripe’s native Stripe.V2.Core.EventNotification. Its fetchEvent() and fetchRelatedObject() methods use the project-owned client and preserve Stripe context. Snapshot and thin destinations use different signed payload shapes; the package rejects a payload that does not match the configured mode.

Delivery behavior

Stripe retries unsuccessful live-mode webhook deliveries for up to three days and sandbox deliveries three times over several hours. Ordering is not guaranteed and duplicate delivery is possible. Claim event.id in application-owned durable storage before dispatch when duplicate admission is unacceptable. Business operations should remain idempotent because separate Event objects can describe the same resource change.

Snapshot event objects are tied to the API version selected for the event destination. Keep that version aligned with the installed SDK types, or narrow and validate resource fields in application code.

Flue forwards a verified event type that is newer than the installed Stripe declarations. Until the project upgrades Stripe, use switch (event.type as string) to observe that future type and treat its resource fields as untrusted rather than weakening the native narrowing for every known event.

The official Stripe SDK selects its Fetch and Web Crypto implementation in Cloudflare Workers. The example executes that path in workerd with Flue’s required nodejs_compat configuration. Projects may initialize credentials through process.env or typed Worker bindings and should still verify their complete target build and workerd tests. Stripe’s declarations reference @types/node; that package is type-only and does not add Node code to the Worker bundle.

The channel does not register event destinations, rotate signing secrets, manage OAuth or API keys, deduplicate events, restore ordering, or define generic Stripe tools. It also does not claim support for specialized latency-sensitive workflows such as synchronous real-time Issuing authorization decisions.

See the @flue/stripe API reference.