{"id":31532603,"url":"https://github.com/vercel/acp-handler","last_synced_at":"2025-10-04T03:56:43.492Z","repository":{"id":317256061,"uuid":"1066643198","full_name":"vercel/acp-handler","owner":"vercel","description":"Integrate the Agentic Commerce Protocol (ACP) into your servers","archived":false,"fork":false,"pushed_at":"2025-10-03T13:54:50.000Z","size":261,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-10-03T14:39:12.388Z","etag":null,"topics":["acp","agentic","commerce","express","hono","nextjs","protocol"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vercel.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-29T19:07:02.000Z","updated_at":"2025-10-03T13:54:53.000Z","dependencies_parsed_at":"2025-10-03T14:39:13.722Z","dependency_job_id":null,"html_url":"https://github.com/vercel/acp-handler","commit_stats":null,"previous_names":["blurrah/agentic-commerce-protocol-template","blurrah/agentic-commerce-protocol-sdk","blurrah/acp-handler","vercel/acp-handler"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/vercel/acp-handler","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vercel%2Facp-handler","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vercel%2Facp-handler/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vercel%2Facp-handler/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vercel%2Facp-handler/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vercel","download_url":"https://codeload.github.com/vercel/acp-handler/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vercel%2Facp-handler/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278262444,"owners_count":25957938,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-04T02:00:05.491Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["acp","agentic","commerce","express","hono","nextjs","protocol"],"created_at":"2025-10-04T03:56:37.911Z","updated_at":"2025-10-04T03:56:43.484Z","avatar_url":"https://github.com/vercel.png","language":"TypeScript","funding_links":[],"categories":["💻 Notable Github Projects"],"sub_categories":["A2A (Agent2Agent Protocol)"],"readme":"# acp-handler\n\nA TypeScript handler for implementing the [Agentic Commerce Protocol](https://developers.openai.com/commerce) (ACP) in your web application. Handle ACP checkout requests with built-in idempotency, signature verification, and OpenTelemetry tracing.\n\n## What is ACP?\n\nAn open standard for programmatic commerce flows between buyers, AI agents, and businesses. This package handles the protocol implementation so you can focus on your business logic.\n\n**Key Features:**\n- ✅ Full ACP spec compliance\n- ✅ Type-safe TypeScript API\n- ✅ Built-in idempotency (prevents double-charging)\n- ✅ OpenTelemetry tracing support\n- ✅ Web Standard APIs (works with Next.js, Hono, Express, Cloudflare Workers, Deno, Bun, Remix)\n- ✅ Production-ready patterns\n- ✅ Comprehensive test suite\n\n## Installation\n\n```bash\npnpm add acp-handler\n```\n\n### Peer Dependencies\n\nThe handler requires a key-value store for session storage. Redis is recommended:\n\n```bash\npnpm add redis\n```\n\nOptional dependencies:\n```bash\npnpm add next  # For Next.js catch-all route helper\n```\n\n## Quick Start\n\n### 1. Implement Required Handlers\n\n```typescript\nimport { createHandlers } from 'acp-handler';\n\nconst handlers = createHandlers(\n  {\n    // Product pricing logic\n    products: {\n      price: async ({ items, customer, fulfillment }) =\u003e {\n        // Fetch products from your database\n        const products = await db.products.findMany({\n          where: { id: { in: items.map(i =\u003e i.id) } }\n        });\n\n        // Calculate pricing\n        const itemsWithPrices = items.map(item =\u003e {\n          const product = products.find(p =\u003e p.id === item.id);\n          return {\n            id: item.id,\n            title: product.name,\n            quantity: item.quantity,\n            unit_price: { amount: product.price, currency: 'USD' }\n          };\n        });\n\n        const subtotal = itemsWithPrices.reduce(\n          (sum, item) =\u003e sum + item.unit_price.amount * item.quantity,\n          0\n        );\n\n        return {\n          items: itemsWithPrices,\n          totals: {\n            subtotal: { amount: subtotal, currency: 'USD' },\n            grand_total: { amount: subtotal, currency: 'USD' }\n          },\n          ready: true, // Ready for payment\n        };\n      }\n    },\n\n    // Payment processing\n    payments: {\n      authorize: async ({ session, delegated_token }) =\u003e {\n        // Integrate with your payment provider (Stripe, etc.)\n        const intent = await stripe.paymentIntents.create({\n          amount: session.totals.grand_total.amount,\n          currency: session.totals.grand_total.currency,\n          payment_method: delegated_token,\n        });\n\n        if (intent.status === 'requires_capture') {\n          return { ok: true, intent_id: intent.id };\n        }\n        return { ok: false, reason: 'Authorization failed' };\n      },\n\n      capture: async (intent_id) =\u003e {\n        const intent = await stripe.paymentIntents.capture(intent_id);\n        if (intent.status === 'succeeded') {\n          return { ok: true };\n        }\n        return { ok: false, reason: 'Capture failed' };\n      }\n    },\n\n    // Webhook notifications\n    webhooks: {\n      orderUpdated: async ({ checkout_session_id, status, order }) =\u003e {\n        // Notify ChatGPT about order updates\n        await fetch(process.env.OPENAI_WEBHOOK_URL, {\n          method: 'POST',\n          headers: {\n            'Content-Type': 'application/json',\n            'X-Signature': hmacSign(payload, secret)\n          },\n          body: JSON.stringify({ checkout_session_id, status, order })\n        });\n      }\n    }\n  },\n  {\n    // Storage backend (Redis recommended)\n    store: createStoreWithRedis('acp')\n  }\n);\n```\n\n### 2. Mount Handlers to Your Framework\n\n#### Next.js (App Router)\n\n```typescript\n// app/checkout_sessions/[[...segments]]/route.ts\nimport { createNextCatchAll } from 'acp-handler/next';\n\nconst { GET, POST } = createNextCatchAll(handlers);\n\nexport { GET, POST };\n```\n\n#### Hono\n\nHono natively supports Web Standard APIs, so no adapter needed:\n\n```typescript\n// server.ts\nimport { Hono } from 'hono';\nimport { handlers } from './checkout-handlers';\nimport {\n  parseJSON,\n  validateBody,\n  CreateCheckoutSessionSchema,\n  UpdateCheckoutSessionSchema,\n  CompleteCheckoutSessionSchema,\n} from 'acp-handler';\n\nconst app = new Hono();\n\napp.post('/checkout_sessions', async (c) =\u003e {\n  const parsed = await parseJSON(c.req.raw);\n  if (!parsed.ok) return parsed.res;\n  const validated = validateBody(CreateCheckoutSessionSchema, parsed.body);\n  if (!validated.ok) return validated.res;\n  return handlers.create(c.req.raw, validated.data);\n});\n\napp.get('/checkout_sessions/:id', async (c) =\u003e {\n  const id = c.req.param('id');\n  return handlers.get(c.req.raw, id);\n});\n\napp.post('/checkout_sessions/:id', async (c) =\u003e {\n  const id = c.req.param('id');\n  const parsed = await parseJSON(c.req.raw);\n  if (!parsed.ok) return parsed.res;\n  const validated = validateBody(UpdateCheckoutSessionSchema, parsed.body);\n  if (!validated.ok) return validated.res;\n  return handlers.update(c.req.raw, id, validated.data);\n});\n\napp.post('/checkout_sessions/:id/complete', async (c) =\u003e {\n  const id = c.req.param('id');\n  const parsed = await parseJSON(c.req.raw);\n  if (!parsed.ok) return parsed.res;\n  const validated = validateBody(CompleteCheckoutSessionSchema, parsed.body);\n  if (!validated.ok) return validated.res;\n  return handlers.complete(c.req.raw, id, validated.data);\n});\n\napp.post('/checkout_sessions/:id/cancel', async (c) =\u003e {\n  const id = c.req.param('id');\n  return handlers.cancel(c.req.raw, id);\n});\n```\n\n#### Express / Node.js\n\nThe core handlers use Web Standard `Request`/`Response` objects. For Node.js frameworks like Express, use [`@whatwg-node/server`](https://github.com/ardatan/whatwg-node):\n\n```bash\npnpm add @whatwg-node/server\n```\n\n```typescript\n// server.ts\nimport express from 'express';\nimport { createServerAdapter } from '@whatwg-node/server';\nimport { handlers } from './checkout-handlers';\nimport {\n  parseJSON,\n  validateBody,\n  CreateCheckoutSessionSchema,\n  UpdateCheckoutSessionSchema,\n  CompleteCheckoutSessionSchema,\n} from 'acp-handler';\n\nconst app = express();\n\n// Helper to extract route params\nconst getId = (req: Request) =\u003e req.url.split('/').filter(Boolean)[1];\n\n// POST /checkout_sessions\napp.post('/checkout_sessions', createServerAdapter(async (req: Request) =\u003e {\n  const parsed = await parseJSON(req);\n  if (!parsed.ok) return parsed.res;\n  const validated = validateBody(CreateCheckoutSessionSchema, parsed.body);\n  if (!validated.ok) return validated.res;\n  return handlers.create(req, validated.data);\n}));\n\n// GET /checkout_sessions/:id\napp.get('/checkout_sessions/:id', createServerAdapter(async (req: Request) =\u003e {\n  const id = getId(req);\n  return handlers.get(req, id);\n}));\n\n// POST /checkout_sessions/:id\napp.post('/checkout_sessions/:id', createServerAdapter(async (req: Request) =\u003e {\n  const id = getId(req);\n  const parsed = await parseJSON(req);\n  if (!parsed.ok) return parsed.res;\n  const validated = validateBody(UpdateCheckoutSessionSchema, parsed.body);\n  if (!validated.ok) return validated.res;\n  return handlers.update(req, id, validated.data);\n}));\n\n// POST /checkout_sessions/:id/complete\napp.post('/checkout_sessions/:id/complete', createServerAdapter(async (req: Request) =\u003e {\n  const id = getId(req);\n  const parsed = await parseJSON(req);\n  if (!parsed.ok) return parsed.res;\n  const validated = validateBody(CompleteCheckoutSessionSchema, parsed.body);\n  if (!validated.ok) return validated.res;\n  return handlers.complete(req, id, validated.data);\n}));\n\n// POST /checkout_sessions/:id/cancel\napp.post('/checkout_sessions/:id/cancel', createServerAdapter(async (req: Request) =\u003e {\n  const id = getId(req);\n  return handlers.cancel(req, id);\n}));\n\napp.listen(3000);\n```\n\n**Note:** This approach works with Express, Fastify, Koa, and any Node.js HTTP framework.\n\n#### Other Frameworks\n\nThe handlers use Web Standard APIs and work natively with:\n- Cloudflare Workers\n- Deno Deploy\n- Bun\n- Vercel Edge Functions\n- Remix\n\nJust call the handlers directly with `Request` objects!\n\n### 3. Done!\n\nYour ACP-compliant checkout API is now ready. ChatGPT can create checkout sessions, update cart items, and complete purchases.\n\n## Core Concepts\n\n### Products Handler\n\nCalculates pricing, taxes, and shipping. Called on every create/update.\n\n```typescript\ntype Products = {\n  price(input: {\n    items: Array\u003c{ id: string; quantity: number }\u003e;\n    customer?: CustomerInfo;\n    fulfillment?: FulfillmentInfo;\n  }): Promise\u003c{\n    items: CheckoutItem[];\n    totals: Totals;\n    fulfillment?: Fulfillment;\n    messages?: Message[];\n    ready: boolean; // Can checkout proceed to payment?\n  }\u003e;\n};\n```\n\n### Payments Handler\n\nHandles payment authorization and capture (two-phase commit).\n\n```typescript\ntype Payments = {\n  authorize(input: {\n    session: CheckoutSession;\n    delegated_token?: string;\n  }): Promise\u003c\n    | { ok: true; intent_id: string }\n    | { ok: false; reason: string }\n  \u003e;\n\n  capture(intent_id: string): Promise\u003c\n    | { ok: true }\n    | { ok: false; reason: string }\n  \u003e;\n};\n```\n\n### Webhooks Handler\n\nNotifies ChatGPT about order updates (completion, cancellation, etc.).\n\n```typescript\ntype Webhooks = {\n  orderUpdated(evt: {\n    checkout_session_id: string;\n    status: string;\n    order?: Order;\n  }): Promise\u003cvoid\u003e;\n};\n```\n\n### Storage\n\nProvides a key-value store for session data and idempotency.\n\n```typescript\ntype KV = {\n  get(key: string): Promise\u003cstring | null\u003e;\n  set(key: string, value: string, ttlSec?: number): Promise\u003cvoid\u003e;\n  setnx(key: string, value: string, ttlSec?: number): Promise\u003cboolean\u003e;\n};\n```\n\n**Built-in Redis adapter:**\n```typescript\nimport { createStoreWithRedis } from 'acp-handler';\n\nconst { store } = createStoreWithRedis('namespace');\n```\n\n## Advanced Features\n\n### Signature Verification\n\nVerify that requests are actually from OpenAI/ChatGPT and haven't been tampered with:\n\n```typescript\nimport { createHandlers } from 'acp-handler';\n\nconst handlers = createHandlers(\n  { products, payments, webhooks },\n  {\n    store,\n    signature: {\n      secret: process.env.OPENAI_WEBHOOK_SECRET, // Provided by OpenAI\n      toleranceSec: 300 // Optional: 5 minutes default\n    }\n  }\n);\n```\n\n**How it works:**\n- HMAC-SHA256 signature verification\n- Protects against unauthorized requests\n- Prevents replay attacks (timestamp must be recent)\n- Constant-time comparison (timing attack protection)\n\n**Returns 401 if:**\n- Signature header is missing\n- Timestamp header is missing\n- Signature doesn't match\n- Request is too old (replay attack)\n- Body has been tampered with\n\n**Optional:** Signature verification is disabled by default for easier development. Enable it in production by providing the `signature` config.\n\n### Idempotency\n\nAutomatically handles idempotency for all POST operations to prevent double-charging:\n\n```typescript\n// Automatically handled by acp-handler\nPOST /checkout_sessions/:id/complete\nHeaders:\n  Idempotency-Key: idem_abc123\n\n// Retries with same key return cached result\n// Payment only charged once!\n```\n\n### OpenTelemetry Tracing\n\nAdd distributed tracing to monitor performance:\n\n```typescript\nimport { trace } from '@opentelemetry/api';\n\nconst tracer = trace.getTracer('my-shop');\n\nconst handlers = createHandlers(\n  { products, payments, webhooks },\n  { store, tracer } // Add tracer\n);\n```\n\n**Spans created:**\n- `checkout.create`, `checkout.update`, `checkout.complete`\n- `products.price` - See pricing performance\n- `payments.authorize`, `payments.capture` - Track payment operations\n- `session.get`, `session.put` - Monitor storage\n- `webhooks.orderUpdated` - Track webhook delivery\n\n**Attributes:**\n- `session_id`, `idempotency_key`, `payment_intent_id`\n- `items_count`, `session_status`, `idempotency_reused`\n\n### Testing\n\nThe package provides test helpers for integration testing:\n\n```typescript\nimport { createMemoryStore, createMockProducts } from 'acp-handler/test';\n\nconst handlers = createHandlers(\n  {\n    products: createMockProducts(),\n    payments: createMockPayments(),\n    webhooks: createMockWebhooks()\n  },\n  { store: createMemoryStore() }\n);\n\n// Test complete checkout flow\nconst res = await handlers.create(req, { items: [...] });\nconst session = await res.json();\n// ...\n```\n\n## Examples\n\nSee the [`examples/basic`](./examples/basic) directory for a complete Next.js implementation with:\n- AI chat demo (simulate ChatGPT)\n- Complete checkout flow\n- Mock products, payments, and webhooks\n- Redis storage\n\n```bash\ncd examples/basic\npnpm install\npnpm dev\n```\n\n## API Reference\n\n### `createHandlers(handlers, options)`\n\nCreates checkout handlers implementing the ACP spec.\n\n**Parameters:**\n- `handlers.products: Products` - Product pricing implementation\n- `handlers.payments: Payments` - Payment processing implementation\n- `handlers.webhooks: Webhooks` - Webhook notifications\n- `options.store: KV` - Key-value storage backend\n- `options.tracer?: Tracer` - OpenTelemetry tracer (optional)\n\n**Returns:** Handlers object with methods:\n- `create(req, body)` - POST /checkout_sessions\n- `update(req, id, body)` - POST /checkout_sessions/:id\n- `complete(req, id, body)` - POST /checkout_sessions/:id/complete\n- `cancel(req, id)` - POST /checkout_sessions/:id/cancel\n- `get(req, id)` - GET /checkout_sessions/:id\n\n### `createNextCatchAll(handlers, schemas?)`\n\nCreates Next.js catch-all route handlers.\n\n```typescript\nimport { createNextCatchAll } from 'acp-handler/next';\n\nconst { GET, POST } = createNextCatchAll(handlers);\nexport { GET, POST };\n```\n\n### `createStoreWithRedis(namespace)`\n\nCreates a Redis-backed KV store.\n\n```typescript\nimport { createStoreWithRedis } from 'acp-handler';\n\n// Uses REDIS_URL environment variable\nconst { store } = createStoreWithRedis('acp');\n```\n\n### `createOutboundWebhook(config)`\n\nHelper for signing outbound webhooks to ChatGPT.\n\n```typescript\nimport { createOutboundWebhook } from 'acp-handler';\n\nconst webhook = createOutboundWebhook({\n  webhookUrl: process.env.OPENAI_WEBHOOK_URL,\n  secret: process.env.OPENAI_WEBHOOK_SECRET,\n  merchantName: 'YourStore'\n});\n\nawait webhook.orderUpdated({\n  checkout_session_id: session.id,\n  status: 'completed',\n  order: { id: 'order_123', status: 'placed' }\n});\n```\n\n## Project Structure\n\n```\nagentic-commerce-protocol-template/\n├── packages/\n│   └── sdk/                    # acp-handler package\n│       ├── src/\n│       │   ├── checkout/       # Checkout implementation\n│       │   │   ├── handlers.ts # Core business logic\n│       │   │   ├── next/       # Next.js adapter\n│       │   │   ├── hono/       # Hono adapter\n│       │   │   ├── storage/    # Storage adapters\n│       │   │   ├── webhooks/   # Webhook helpers\n│       │   │   └── tracing.ts  # OpenTelemetry helpers\n│       │   ├── feeds/          # Product feeds (coming soon)\n│       │   └── index.ts\n│       └── test/               # Test helpers\n└── examples/\n    └── basic/                  # Example Next.js app\n```\n\n## Resources\n\n- [ACP Checkout Spec](https://developers.openai.com/commerce/specs/checkout)\n- [ACP Product Feeds Spec](https://developers.openai.com/commerce/specs/feed)\n- [Apply for ChatGPT Checkout](https://chatgpt.com/merchants)\n- [Example Implementation](./examples/basic)\n\n## Contributing\n\nContributions welcome! Please open an issue or PR.\n\n## License\n\nMIT\n\n---\n\n**Questions?** Open an issue or check the [ACP documentation](https://developers.openai.com/commerce).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvercel%2Facp-handler","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvercel%2Facp-handler","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvercel%2Facp-handler/lists"}