1. Everything already works on its own…
By now you already understand how the commerce flow spins around ChatGPT. The merchant has a product feed, ACP endpoints are implemented (/checkout_sessions and friends), Instant Checkout processes the payment, and the backend receives webhooks and creates orders. All of this can work even without your ChatGPT App: a Product Feed + ACP backend is enough.
Separately, you already know how to:
- build a Product Feed according to the OpenAI specification;
- design and implement Agentic Checkout / Delegated Payment;
- write a ChatGPT App with a widget and MCP tools for gift discovery.
Individually, everything looks great, but together it easily turns into a “zoo of services.” The widget lives its own life, the MCP server another, the ACP backend yet another, and the order and webhook logic a fourth. At the first attempt to debug a real purchase or fix a weird bug, you suddenly realise nobody truly sees the big picture.
The goal of this lecture is to pull you out of that state and give you a cohesive yet implementable architecture: how exactly the Product Feed is connected to the ACP backend, how both relate to the ChatGPT App and the widget, where the payment provider appears in the picture, and how all this is wrapped into team‑friendly components: services, databases, and APIs.
At the same time, we will constantly emphasise what is a hard standard (SPEC) and what is merely our architectural choice for GiftGenius.
Insight: ChatGPT is a free Google
ChatGPT works with users roughly like Google: it brings you relevant traffic for free, because it earns money elsewhere—on the users themselves.
From a business perspective, this means a simple thing: ChatGPT becomes a free “ad channel” for your products, provided you have connected the Product Feed and the ACP backend. The model will recommend your items if they satisfy the user’s request well, and you do not need to pay separately for impressions or clicks.
Two practical takeaways follow:
- The window of opportunity is TEMPORARILY very cheap. Competition in the ACP ecosystem is currently low, and you can reach higher price segments without the usual ad budgets. It’s a rare situation where high‑conversion traffic for expensive products (air travel, real estate, premium goods, insurance) may cost you nothing.
- It makes sense to start with the highest‑margin verticals. If you have access to categories with a high order value, it’s rational to connect them first:
- sale/rental of airplanes, yachts, villas;
- sale/rental of houses and premium real estate;
- jewellery, luxury watches, insurance products and services.
This does not guarantee “quick millions,” but it creates an asymmetry: teams that first launch a high‑quality Product Feed and a reliable ACP backend in expensive segments will gain a disproportionately high benefit from the channel while it remains undervalued and effectively free.
2. GiftGenius reference architecture: broad strokes
Let’s start from the bird’s‑eye view. Recall the overall picture from previous modules: the user writes in ChatGPT, the model invokes your tools, and the commerce layer lives in a separate backend.
Let’s outline the main GiftGenius blocks.
First, the ChatGPT UI and the GPT model, which conduct the conversation with the user and, if necessary, connect the GiftGenius App (or even work without the App—purely via the Product Feed).
Second, the GiftGenius widget (Next.js + Apps SDK), which shows gift cards and, as needed, checkout progress. It lives in the window.openai sandbox and knows nothing about real payment credentials.
Third, the MCP layer, which gives the model tools to search for gifts in the catalogue (Product Feed) and, possibly, to read order history.
Fourth, the commerce/ACP backend, which:
- reads the Product Feed as the source of truth for products and SKUs;
- implements the Agentic Checkout Spec (/checkout_sessions, webhooks, statuses);
- talks to the payment provider (for example, Stripe) via the Delegated Payment Spec.
Fifth, the databases of the catalogue (if the feed is built from a DB), orders, and auxiliary structures (users, settings).
And finally, the payment provider, which stores and processes payment data and sends webhooks about payment results.
Schematically, it looks like this:
graph LR U[User in ChatGPT] --> GPT[GPT model] GPT -->|renders| W[GiftGenius Widget
Next.js + Apps SDK] GPT -->|MCP tools| MCP[MCP server
gift search] MCP --> PF["Product Feed
(DB/JSON)"] GPT -->|ACP HTTP| ACP[GiftGenius Commerce Backend
Agentic Checkout] ACP --> ORDERS[Orders database] ACP --> PSP["Payment service provider
(Stripe, etc.)"] PSP --> ACP ACP -->|webhooks/events| GPT
This diagram describes the GiftGenius architecture as an example implementation. The Product Feed format, the /checkout_sessions contract, and the Delegated Payment protocol remain part of the ACP standard; the placement of services, DB schemas, and process split are your architectural choice.
3. How the Product Feed, ACP, and the widget are logically connected
To avoid drowning in arrows, let’s lock in a simple but crucial idea: you have exactly one source of truth for products.
In GiftGenius, let it be a products + skus table in PostgreSQL. From it you:
- produce the Product Feed according to the OpenAI specification (directly or via an export);
- build a search index for MCP tools (for example, search_gifts);
- validate ACP backend requests—check that the incoming sku_id actually exists and has the correct price and currency.
Thus, MCP search and ACP checkout look at the same data, and the widget merely displays results that come either from MCP tools or indirectly from ACP (for example, order information).
You can think of this as two “windows” into the same catalogue: one window for search and recommendations, the other for checkout. If these windows look into different databases, you’re in for an exciting life of mismatches.
4. Modeling the data: from Product Feed to order
Let’s start with simple TypeScript types that will live in your GiftGenius repository (for example, in src/domain/commerce.ts). These types are not a literal copy of the specs, but they capture their core ideas in a form convenient for the app.
// src/domain/commerce.ts
export interface ProductSku {
id: string; // stable SKU ID (matches the Product Feed)
title: string; // human-readable title
priceCents: number; // price in cents
currency: string; // ISO code, e.g., "usd"
}
export type CheckoutStatus = "pending" | "succeeded" | "failed";
export interface CheckoutSession {
id: string;
skuId: string;
totalCents: number;
currency: string;
status: CheckoutStatus;
}
Here we explicitly carry into CheckoutSession a reference to skuId and a fixed currency/amount. This is our internal model; the real Agentic Checkout Spec is richer, but the basic idea is the same: a session is “how much, for what, and in what status.”
Next we need an order type:
export interface Order {
id: string;
userId: string;
skuId: string;
totalCents: number;
currency: string;
checkoutSessionId: string;
status: "awaiting_payment" | "paid" | "canceled" | "refunded";
}
You can feel the influence of the common entities from the previous module: intent, checkout_session, order. In our training project, we slightly collapse intent and order to avoid multiplying entities, but we keep the link to checkoutSessionId.
5. How the GiftGenius widget peeks into the commerce world
An important point: the widget by itself does not call the payment system and does not even need to know ACP details; its role is to show the user the state computed and fixed on the backends.
The simplest useful scenario: after a successful purchase, the user can return to the chat and ask, “Show my latest orders in GiftGenius.” GPT will call an MCP tool like get_user_orders, which will hit your backend, and the widget will display the list.
Imagine a Next.js API route that returns the latest orders (simplified):
// app/api/orders/recent/route.ts
import { NextRequest, NextResponse } from "next/server";
import { getRecentOrdersForUser } from "@/lib/orders";
export async function GET(req: NextRequest) {
const userId = req.headers.get("x-giftgenius-user-id")!;
const orders = await getRecentOrdersForUser(userId);
return NextResponse.json({ orders });
}
The getRecentOrdersForUser function already lives in your commerce layer, works with the DB, and knows the order structure. The widget, in turn, can call this route via window.fetch (as we did in previous modules) and display purchase cards.
The combination “MCP tool → your API → orders DB → widget” gives the user the feeling that the App has “memory” of purchases, although the widget simply displays the backend state.
6. A simple ACP endpoint implementation in the Next.js style
Now let’s outline how a training implementation of one of the key ACP endpoints—creating a checkout_session—might look. The spec has a fairly rich contract, but for the course we can keep only the essence: a skuId arrives, we validate it against the feed/DB, create a session, and return its ID and amount.
Let’s say we have the route POST /api/checkout-sessions:
// app/api/checkout-sessions/route.ts
import { NextRequest, NextResponse } from "next/server";
import { findSkuById, createCheckoutSession } from "@/lib/checkout";
export async function POST(req: NextRequest) {
const body = await req.json(); // { skuId: string }
const sku = await findSkuById(body.skuId);
if (!sku) {
return NextResponse.json(
{ error: "SKU not found" },
{ status: 400 },
);
}
const session = await createCheckoutSession(sku);
return NextResponse.json({ session });
}
There are a few important points here.
First, this is where the commerce layer reconciles with the Product Feed/DB: findSkuById must look into the same source the feed is generated from. We don’t trust anything that came “out of thin air”—neither from GPT nor from the widget.
Second, we return only what ChatGPT/ACP clients need: session ID, amount, currency, and status (by default pending or not_ready_for_payment, depending on your chosen terminology). In real ACP there are more fields, including available payment methods and fulfillment details, but the training example focuses on the initial session creation.
Third, this route is convenient to cover with contract tests: if tomorrow the Product Feed structure changes, tests for findSkuById and createCheckoutSession should catch it before ChatGPT starts showing users odd errors.
7. Linking ACP sessions and the payment provider
So far, we haven’t touched the payment provider. In a real integration, roughly the following happens (simplified).
First, ChatGPT (via ACP) calls your POST /checkout_sessions. Your backend creates a local session in its DB. When the user confirms payment in the Instant Checkout UI, the platform requests a delegated payment token (Shared Payment Token) from the PSP for the specific merchant and amount. This token arrives to you in the complete request (or an analogous call per the Delegated Payment Spec).
After that, you create a payment at the PSP using the token, without access to the actual payment data. The PSP sends a webhook with the result; you update the order and/or checkout session status.
In our training code, we can limit ourselves to simulating this step. For example, the completeCheckoutSession function might look like this:
// src/lib/checkout.ts
export async function completeCheckoutSession(sessionId: string, spt: string) {
// In reality, this calls the PSP API with the delegated token (SPT)
const paymentOk = await mockChargeWithToken(spt);
return paymentOk
? { status: "succeeded" as const }
: { status: "failed" as const };
}
Calling the PSP and using the Shared Payment Token is part of the Delegated Payment standard, and the mockChargeWithToken function is our training architectural layer simulating this specificity.
8. GiftGenius end‑to‑end flow: from request to a paid gift
Now let’s put it all together as a sequence of steps. This is the “production‑like” GiftGenius story we combine all layers for. It’s important not to mix two different worlds, so we’ll consider them separately.
Scenario A: no App, only Product Feed + ACP
In this scenario, you have a Product Feed and an ACP backend, but no ChatGPT App or widget. This is a classic Instant Checkout merchant.
The user writes in ChatGPT something like: “Pick a digital gift up to $50.” GPT uses your Product Feed to find suitable SKUs and shows them in its native UI as shopping cards. There is no React code from you yet—the cards are rendered entirely by ChatGPT.
The user clicks the “Buy” button on one of those cards. That click is handled by ChatGPT itself. The platform:
- forms line_items based on the Product Feed;
- calls your POST /checkout_sessions per the Agentic Checkout Spec;
- shows the user the Instant Checkout UI (payment method, address, etc.);
- after confirmation, obtains a Shared Payment Token from the PSP and calls your .../complete;
- receives from you the final checkout_session state and, if necessary, waits for an order webhook.
From your code’s perspective, only the ACP endpoints and the Product Feed operate here. There is no Apps SDK, window.openai, or widget at all. And this is an absolutely valid, “pure” ACP merchant scenario.
Scenario B: with the ChatGPT App and the GiftGenius widget
Now add the ChatGPT App and the GiftGenius widget on top. The Product Feed and ACP backend don’t go anywhere: they still provide search and payment. The difference is that we now have our own UI and step logic inside the App.
Imagine the dialog: the user writes in ChatGPT, “Pick a gift for mom up to $50.” GPT understands that this is a commerce request and proposes using the GiftGenius App. The widget asks a couple of clarifying questions: age, interests, country. After that, GPT calls your MCP tool search_gifts with filters, and the MCP server queries the catalogue (DB or prepared index), finds several suitable SKUs, and returns them in a structured format.
GPT passes this data to the widget, and the widget shows your signature gift cards (React components, carousels, and so on). This is now your design and UX, not ChatGPT’s standard shopping UI.
When the user clicks “Buy” in the widget, something different happens than in Scenario A. This click is handled by the widget:
- The widget knows which SKU the user selected.
- Through its own API (for example, POST /api/checkout-sessions) it calls your backend to create a checkout_session (or to obtain the ID of an already prepared session).
- Then the widget calls an Apps SDK runtime method like:
// See the current method signature in the Apps SDK docs await window.openai.requestCheckout({ checkoutSessionId: session.id, ... });This call is initiated by the widget. For ChatGPT it’s a signal: “It’s time to open Instant Checkout for this checkout_session.”
From there, the ChatGPT platform operates very similarly to Scenario A, but behind the scenes:
- it shows the user the native Instant Checkout UI;
- obtains the Shared Payment Token from the PSP;
- calls your ACP endpoint to complete the session (.../complete);
- participates in receiving and processing webhooks from your backend.
So in Scenario B, the widget launches checkout via the Apps SDK, and the ACP calls (creating/completing a checkout_session) happen either before that (when you create the session in your backend) or after requestCheckout, but always on the server side.
Meanwhile, the widget can show “Checkout” steps, statuses, and an order preview, relying on your API (/api/orders/...) and MCP tools.
If we draw Scenario B as a diagram, it will look something like this:
sequenceDiagram
participant User as User
participant GPT as ChatGPT / GPT
participant W as GiftGenius Widget
participant MCP as MCP server
participant ACP as Commerce Backend
participant PSP as Payment service provider
User->>GPT: "Pick a gift up to $50"
GPT->>MCP: search_gifts(...)
MCP-->>GPT: SKU list
GPT->>W: data to render cards
User->>W: click "Buy"
W->>ACP: POST /api/checkout-sessions (skuId)
ACP-->>W: checkout_session (id, amount, currency)
W->>GPT: window.openai.requestCheckout({ checkoutSessionId })
GPT->>User: UI Instant Checkout
User->>GPT: payment confirmation
GPT->>PSP: request Shared Payment Token
PSP-->>GPT: SPT
GPT->>ACP: complete(sessionId, SPT)
ACP->>PSP: charge(SPT)
PSP-->>ACP: payment result
ACP->>GPT: order status
GPT->>User: message about successful/unsuccessful payment
Key differences from Scenario A:
- In A, the cards and the “Buy” button are rendered by ChatGPT itself, and it initiates ACP calls directly.
- In B, the cards and the “Buy” button are rendered by your widget, and it calls window.openai.requestCheckout(...). Only after that does ChatGPT, under the hood, talk to your ACP backend and the PSP.
Insight
ChatGPT wrote in its SDK that monetisation in apps is coming soon. That’s true. Several not‑yet‑announced methods are already available to widgets. And the most interesting of them is requestCheckout().
Its call looks like this:
window.openai.requestCheckout({
id: "checkout_session_123",
payment_provider: {
merchant_id: "stripe",
supported_payment_methods: ["card"]
},
...
}
It displays a dialog window that lets the user complete the payment. So design your app as if monetisation were already enabled: by the time you finish your work, that’s how it will be.
9. Mini implementation for the course: a monolithic backend
In the architecture modules, we already raised the question: should you do everything in a single service, or immediately split into an MCP server, a commerce backend, and a separate service for the payment integration? For educational purposes, a “near‑monolith” is usually sufficient: one repository, one deployment, but the logic is carefully separated by layers.
A training version of GiftGenius might look like this: a Next.js application where:
- the widget lives in app/widget/page.tsx;
- ACP endpoints live in app/api/checkout-sessions and neighbouring routes;
- MCP tools live in app/api/mcp/route.ts or a separate folder;
- order handling lives in src/lib/orders.ts, src/lib/checkout.ts, and nearby modules.
Physically, this is one server (especially at the dev/staging stage), but logically you already think in terms of three roles: UI (widget), MCP (tools/resources for GPT), and ACP (commerce backend).
Later, in the production modules, you’ll see how this “monolith” is split across several services and environments, with an MCP Gateway in front. But at the level of module 14, such a “monolith with the right layers” already gives a very realistic architecture.
10. Practical assignment: your architecture around ACP
To keep everything above from remaining theoretical, it makes sense to apply this to your domain right now. Within this lecture, you can do two mini‑exercises.
First, choose your own scenario: SaaS subscription, booking, food delivery, online courses—any case where there is a product/service, a price, and a reasonable checkout. Recall the phase model: discovery → decision → checkout → post‑payment.
Second, based on the GiftGenius architecture, describe in free form: how you will build the Product Feed (where SKUs and prices live, who updates them), where you will implement the ACP contract (a separate service or part of an existing backend), how you will connect the payment provider, and how your widget (if you have one) will interact with all this via MCP and the Apps SDK.
It’s useful to state explicitly whether your project will use only Scenario A (Instant Checkout without an App), only Scenario B (App + widget), or both scenarios at once. Even such a textual architectural sketch significantly reduces the risk of surprises during real integration.
11. Common mistakes when integrating the Product Feed, ACP, and the widget
Error No. 1: two different catalogues—one for search, another for checkout.
Sometimes a team first spins up a quick “search” feed for GPT (e.g., a small JSON), and then separately builds a commerce DB for orders. If you don’t link them with common IDs and a shared update logic, GPT may offer users products that can no longer be purchased or at an outdated price. The right approach is a single source of truth from which both the Product Feed and the internal tables for ACP endpoints are generated.
Error No. 2: trusting data that came from GPT or the widget.
When a checkout_session arrives with a skuId and a price, it’s very tempting to just trust these values: “well, GPT wouldn’t lie.” But the model can easily “get creative” or mix up a SKU, and a user can try to tamper with a request. If you don’t reconcile incoming data with the Product Feed/DB, you risk selling the wrong thing for the wrong price. Any ACP endpoint must start with validation against the primary catalogue store.
Error No. 3: mixing the roles of the widget and the commerce backend.
Sometimes developers, out of habit, invoke a payment SDK directly from the frontend, create sessions in Stripe, and behave like on a regular website. In the context of ChatGPT Apps, this breaks the security model and contradicts ACP: the payment flow should pass through ChatGPT and your commerce backend, and the widget should only display state and send events (like requestCheckout). If the widget knows too much about the payment perimeter, you get both complexity and increased risk.
Error No. 4: oversimplifying the ACP contract.
In the training example, we intentionally keep only skuId, amount, and status so as not to drown in details. The problem starts when such a “demo contract” quietly slips into production. You suddenly discover that you’re missing fields for address, taxes, shipping methods, promo codes, and you start “bolting” them on chaotically. It’s better to design internal models with headroom for real scenarios from the start, even if some fields remain unused for a while.
Error No. 5: no link between orders and users.
In a demo, it’s easy to limit yourself to orderId and skuId, without thinking how a user will return in a week and ask: “Show my purchases.” If you don’t put a userId (or another stable identifier) into the order and checkout session from the start, you’ll later have to do migrations and build complex bridges. Commerce architecture around ChatGPT almost always assumes that GPT can link the current dialog to the user’s order history—plan for this in advance.
Error No. 6: underestimating the importance of webhooks and idempotency.
In this lecture we only talked about webhooks; you’ll go deeper in the next modules. It’s easy to think: “well, a webhook will arrive once, we’ll update the order—and that’s it.” In practice, payment systems like to retry events, and the network likes to drop responses. If you don’t design orders and checkout sessions as idempotent structures (by checkoutSessionId or paymentId), you can get double charges, duplicate orders, and non‑obvious discrepancies between the PSP and your DB.
Error No. 7: ignoring constraints and policy in the Product Feed.
Chasing a quick demo feed, it’s easy to forget about age restrictions, country availability, prohibited categories, and other “details.” Then it turns out GPT cheerfully recommends a product that cannot be sold to this user in their region or at their age. Fields related to policy and restrictions must be designed and populated from the start, even if you’re currently selling only harmless digital gifts.
GO TO FULL VERSION