Commerce and Email Channels

Purpose and Scope

Commerce, payments, email, and marketing systems usually reach Flue through webhooks: provider-owned HTTP deliveries that must be verified before they affect agents or durable application state. In Flue terminology, a channel is the ingress boundary for those deliveries. It verifies the provider request, parses provider-native data, and calls the application handler. The handler can then dispatch an agent, invoke project code, or return the response shape expected by the provider. This page focuses on Shopify, Stripe, Resend, and Salesforce Marketing Cloud Engagement because their channels share the same boundary while differing in signatures, retry rules, payload shape, and timing constraints.

Sources: apps/docs/src/content/docs/guide/channels.md, packages/shopify/README.md, packages/stripe/README.md, packages/resend/README.md, packages/salesforce-marketing-cloud/README.md

Flue intentionally does not turn these integrations into full provider clients. The channel owns inbound verification, protocol details, body parsing, and route discovery below /channels/<name>/...; your application owns outbound SDK clients, OAuth or API keys, durable deduplication, business persistence, and authorization policy for tools or agents. This separation matters most for commerce and email because webhook events often start long-running work, but provider APIs, installation state, and reply policy are domain-specific. The recommended pattern is to acknowledge verified ingress quickly after admitting durable work, then let agents or workflows continue through Flue runtime primitives.

Sources: apps/docs/src/content/docs/guide/channels.md

Relevant Source Files

  • apps/docs/src/content/docs/guide/channels.md — defines the shared channel concept, flue add channel blueprint flow, file-based routing, and ownership boundary between channel packages and application code.
  • packages/shopify/README.md — documents verified Shopify JSON webhook ingress, route placement, callback inputs, response behavior, timing, and deduplication responsibilities.
  • packages/stripe/README.md — documents verified Stripe webhook ingress using a project-owned Stripe client, event payload mode, and future event type handling.
  • packages/resend/README.md — documents verified Resend webhook ingress, official SDK verification, delivery acknowledgment semantics, and application-owned email behavior.
  • packages/resend/package.json — records the @flue/resend package export, Node engine, and peer dependency requirements for resend, @types/node, and @types/react.
  • packages/salesforce-marketing-cloud/README.md — documents Salesforce Marketing Cloud Engagement ENS verification, event batch forwarding, optional setup verification, retry semantics, and application-owned lifecycle behavior.

Shared Channel Model

A first-party channel module is normally created by flue add channel <provider> and placed under the project source root, for example src/channels/resend.ts or src/channels/stripe.ts. The immediate filename becomes the route namespace, and the module exports a named channel binding that Flue discovers. The same module may also export a named client initialized with the provider SDK, plus helper functions or tools owned by the application. This convention keeps ingress declarative while leaving outbound calls in normal TypeScript modules where credentials, retries, and business policy can be reviewed like the rest of the app.

Sources: apps/docs/src/content/docs/guide/channels.md

For commerce and email providers, the handler should treat verified payloads as a starting signal rather than the complete business transaction. Shopify payload fields vary by topic, API version, and subscription field selection. Stripe event unions follow the installed SDK version, even though verified future event types may still arrive. Resend forwards official webhook event payloads, including event types newer than the installed SDK version. Salesforce Marketing Cloud forwards ENS events with provider-native field names and optional unknown fields. In each case, validate the fields you consume, preserve provider identifiers for idempotency, and dispatch durable work before returning an acknowledgment.

Sources: packages/shopify/README.md, packages/stripe/README.md, packages/resend/README.md, packages/salesforce-marketing-cloud/README.md

Provider Patterns

Shopify uses createShopifyChannel with a clientSecret and exposes one fixed POST /webhook route, served as POST /channels/shopify/webhook when the file is channels/shopify.ts. The channel verifies Shopify's base64 HMAC-SHA256 over the exact request bytes before parsing JSON. The callback receives the Hono context, parsed payload, and verified rawBody; delivery metadata such as x-shopify-topic, x-shopify-shop-domain, and x-shopify-webhook-id is read from native headers. Shopify allows five seconds for a delivery, retries non-2xx responses, does not guarantee ordering, and can redeliver events, so deduplication remains application-owned.

Sources: packages/shopify/README.md

Stripe uses createStripeChannel with a project-owned Stripe client and a webhookSecret. The README example constructs the client with Stripe.createFetchHttpClient(), then switches on event.type inside the webhook callback for events such as checkout.session.completed and checkout.session.async_payment_succeeded. The route is POST /channels/stripe/webhook. If the application sets eventPayload: 'thin', it receives verified API v2 event notifications instead of snapshot events. Outbound API calls, tools, credentials, deduplication, and persistence remain outside the channel package.

Sources: packages/stripe/README.md

Resend uses createResendChannel with the official Resend client and RESEND_WEBHOOK_SECRET. The package verifies the exact body and signed svix-* headers before calling the application webhook. Verified deliveries are forwarded as the official WebhookEventPayload union with provider-native fields such as event.type, created_at, and data, and delivery.id is available for deduplication. Returning nothing or a JSON-compatible value acknowledges with 200; returning a Hono or Fetch Response passes through. Resend retries every status other than 200, so non-200 responses should be deliberate.

Sources: packages/resend/README.md, packages/resend/package.json

Salesforce Marketing Cloud Engagement uses createSalesforceMarketingCloudChannel for Event Notification Service ingress, not generic Salesforce APIs. The route is POST /channels/salesforce-marketing-cloud/events. Signed notifications require x-sfmc-ens-signature, a base64 HMAC-SHA256 digest over exact request bytes using the opaque callback key as UTF-8 HMAC material. The callback receives an ordered, nonempty batch of at most 1000 events and the decoded raw body. ENS retries unacknowledged batches for up to seven days, so handlers should admit durable work quickly and make non-idempotent processing idempotent.

Sources: packages/salesforce-marketing-cloud/README.md

Compact Reference

ProviderPackage entry pointDiscovered routeVerification materialHandler shapeApplication-owned responsibilities
ShopifycreateShopifyChannel from @flue/shopifyPOST /channels/shopify/webhookclientSecret; HMAC over exact request byteswebhook({ c, payload, rawBody })OAuth, access tokens, webhook registration, deduplication, Admin API behavior
StripecreateStripeChannel from @flue/stripePOST /channels/stripe/webhookwebhookSecret; official Stripe SDK verification through project clientwebhook({ event })Outbound API calls, credentials, tools, persistence, deduplication
ResendcreateResendChannel from @flue/resendPOST /channels/resend/webhookwebhookSecret; exact body plus signed svix-* headerswebhook({ event, delivery })Domain setup, webhook registration, full email retrieval, attachments, replies, outbound mail
Salesforce Marketing CloudcreateSalesforceMarketingCloudChannel from @flue/salesforcePOST /channels/salesforce-marketing-cloud/eventssignatureKey; x-sfmc-ens-signature HMAC over exact request bytesevents({ batch }); optional verification handlerENS callback registration, OAuth, token lifecycle, subscriptions, family validation, deduplication

The most important operational distinction is the acknowledgment contract. Shopify retries non-2xx responses and has a five-second delivery window. Stripe verification and event parsing happen through the project-owned SDK client. Resend retries anything other than 200. Salesforce Marketing Cloud accepts 200 through 204 and has a separate unsigned callback setup flow that must answer within 30 seconds if enabled. Across all four, Flue imposes the channel boundary and route discovery, but durable acceptance, deduplication, and long-running business logic belong in your application.

Sources: packages/shopify/README.md, packages/stripe/README.md, packages/resend/README.md, packages/salesforce-marketing-cloud/README.md

Implementation Flow

A practical implementation starts by adding the provider blueprint, then reviewing the generated channel module before connecting it to production traffic. Keep the named channel export focused on verifying and translating ingress. Initialize the provider SDK client as a named client export or adjacent application module, and expose only the operations that agents or workflows need as tools. For example, a Resend email-received handler can dispatch a message-scoped agent with the delivery id and email id, while a project-owned retrieval tool uses the Resend client to fetch full content later under application policy.

Sources: apps/docs/src/content/docs/guide/channels.md, packages/resend/README.md

When the provider payload can cause side effects, prefer a durable admission step over doing all work inline. Store provider delivery identifiers such as Shopify's x-shopify-webhook-id, Resend's delivery.id, Stripe event ids from the SDK event object, or Salesforce event-family identifiers after you narrow the ENS event. Then dispatch the appropriate agent or workflow and return the provider's success response. This avoids retry storms, supports recovery, and gives the application a clear place to enforce idempotency, authorization, and replay handling.

Sources: packages/shopify/README.md, packages/stripe/README.md, packages/resend/README.md, packages/salesforce-marketing-cloud/README.md

Testing and Next Steps

Test each channel at the boundary it owns: valid signatures, invalid signatures, exact body preservation, provider setup handshakes, route placement, JSON-compatible handler returns, and pass-through Response values. Also test the responsibilities the application owns: deduplication records, token loading, outbound SDK failures, agent dispatch inputs, and field narrowing for provider-specific event families. For Resend, remember the peer declaration requirements from the package metadata: resend, @types/node, and @types/react are declared as peers, while the README clarifies that the type packages are declaration-only and do not add Node or React runtime code to a Worker bundle.

Sources: packages/resend/README.md, packages/resend/package.json

Next, read the general Channels guide for discovery and custom-channel blueprint behavior, then pair this page with provider-specific ecosystem documentation when wiring real credentials. If no first-party channel exists for a commerce or email provider, use the generic channel blueprint with the provider's webhook documentation, verify against the unconsumed body, preserve provider-native events, and use the provider's established SDK for outbound calls. That keeps new integrations consistent with the same Flue ownership boundary used by Shopify, Stripe, Resend, and Salesforce Marketing Cloud.

Sources: apps/docs/src/content/docs/guide/channels.md