inflow-payments

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

InFlow Payments Integration

InFlow 支付集成

InFlow is a stablecoin payment platform. Sellers (merchants, agents) request payments from consumers; consumers approve via mobile/email/SMS; settlements happen on-chain. Consumers can pre-configure policies that auto-approve requests within budget — enabling 0-click flows for autonomous agents.
This skill integrates InFlow into any project — backend service, full-stack web app, CLI, autonomous agent, or worker — without forcing a specific framework or language. Important: the integration itself is always backend work. Browser code is only ever the optional
inflow.js
popup (UX only); every REST call to InFlow and the
INFLOW_API_KEY
live exclusively on the server. See the Hard Backend Gate section for the rule and how to handle projects without a backend yet.
InFlow是一个稳定币支付平台。卖家(商户、Agent)向消费者发起支付请求;消费者通过手机/邮件/SMS完成授权;结算在链上进行。消费者可预先配置策略,自动批准预算范围内的请求——为自主Agent实现一键式支付流程。
本技能可将InFlow集成到任意项目中——后端服务、全栈Web应用、CLI、自主Agent或Worker——无需强制使用特定框架或语言。重要提示:集成工作始终属于后端范畴。浏览器端代码仅为可选的
inflow.js
弹窗(仅负责用户体验);所有对InFlow的REST调用以及
INFLOW_API_KEY
必须完全部署在服务器端。请阅读「严格后端限制」章节,了解相关规则以及如何处理尚无后端的项目。

What InFlow Supports

InFlow支持的功能

CapabilityEndpoint(s)Use case
User search
POST /v1/users/search
Find a consumer by email, mobile, or username
User registration
POST /v1/requests/register
Onboard a new consumer (out-of-band approval)
Login
POST /v1/requests/login
Authenticate a consumer (out-of-band approval)
Payment
POST /v1/requests/payment
Request a stablecoin payment from a consumer
Policies
POST /v1/policies
,
POST /v1/requests/policy
Pre-approved spending rules for headless / 0-click flows
WebhooksInbound
POST
to your endpoint
Receive transaction/approval status changes
Polling fallback
GET /v1/requests/{requestId}
Check status without webhooks
Wallet read
GET /v1/balances
,
GET /v1/transactions
Read balances and transaction history
Crypto wallets
GET/POST /v1/deposit-addresses
,
/v1/withdrawal-addresses
Manage on-chain wallet addresses
Agentic users
POST /v1/users/agentic
Create a programmatic account, get an API key
Currencies:
USDC
,
USDT
,
EURC
,
PYUSD
(stablecoins only — no fiat) Blockchains:
APTOS
,
BASE
,
SOLANA
,
WORLD
功能接口使用场景
用户搜索
POST /v1/users/search
通过邮箱、手机号或用户名查找消费者
用户注册
POST /v1/requests/register
引导新消费者完成注册(需离线授权)
登录
POST /v1/requests/login
验证消费者身份(需离线授权)
支付
POST /v1/requests/payment
向消费者发起稳定币支付请求
策略
POST /v1/policies
,
POST /v1/requests/policy
为无界面/一键式流程预配置消费规则
Webhook向你的接口发起入站
POST
请求
接收交易/授权状态变更通知
轮询降级方案
GET /v1/requests/{requestId}
无需Webhook即可查询状态
钱包查询
GET /v1/balances
,
GET /v1/transactions
查询余额和交易历史
加密钱包
GET/POST /v1/deposit-addresses
,
/v1/withdrawal-addresses
管理链上钱包地址
Agent用户
POST /v1/users/agentic
创建程序化账户并获取API密钥
支持币种
USDC
USDT
EURC
PYUSD
(仅稳定币——不支持法币) 支持区块链
APTOS
BASE
SOLANA
WORLD

What This Skill Does NOT Do

本技能不支持的功能

These features are not exposed by the InFlow public API. If the user asks for any of them, stop and tell them they're not supported — do not attempt workarounds:
  • Marketplace splits — No
    splits[]
    field on
    PaymentRequest
    . Multi-recipient payments would need separate sequential requests with no atomic guarantee.
  • Refund creation — Transactions can have status
    REFUNDED
    , but there's no API to initiate one. Refunds are dashboard-only or out-of-band.
  • Webhook URL registration via API — Webhook URLs are configured via the InFlow dashboard. There's no
    POST /v1/webhooks
    to register them programmatically.
  • OAuth flow for sellers — Despite some legacy documentation referencing "OAuth", consumer authentication is entirely SDK-mediated. There's no OAuth dance for the integrating seller.

以下功能未在InFlow公开API中开放。如果用户询问这些功能,请直接告知不支持——不要尝试变通方案:
  • 商城拆分支付——
    PaymentRequest
    中没有
    splits[]
    字段。多收款方支付需要发起独立的连续请求,但无法保证原子性。
  • 创建退款——交易状态可显示为
    REFUNDED
    ,但没有API可发起退款操作。退款仅能通过控制台或离线方式处理。
  • 通过API注册Webhook URL——Webhook URL需通过InFlow控制台配置,没有
    POST /v1/webhooks
    接口支持程序化注册。
  • 卖家OAuth流程——尽管部分旧文档提到"OAuth",但消费者身份验证完全由SDK处理,集成方无需进行OAuth流程。

Defaults & Decision Tree

默认规则与决策树

When the user doesn't specify, use these defaults:
QuestionDefaultWhy
Currency
USDC
Most widely-held stablecoin
userDetails
field
["EMAIL"]
Required field; email is the minimum useful identifier
Display modeSee decision belowDepends on whether a browser is involved
Production vs sandboxSandbox SDK URL until user confirmsProduction SDK URL is not yet documented
Display mode:
FULL
if the consumer is interacting with a browser at the moment of payment (and the page can load
inflow.js
).
HEADLESS
for everything else: server-to-server, autonomous agents, scheduled jobs, AI workers.
Webhook vs polling: Webhooks for production. Polling only for local development, scripts, or anything without a public HTTPS endpoint.
User identification: A payment must be addressed to a known
userId
. The agent must either:
  • Get the
    userId
    from the user (if they already know it), OR
  • Search by email/mobile/username via
    POST /v1/users/search
    , OR
  • Register a new consumer if the search returns 404
Always do user identification BEFORE creating a payment request.

当用户未明确指定时,使用以下默认值:
问题默认值原因
币种
USDC
持有量最广泛的稳定币
userDetails
字段
["EMAIL"]
必填字段;邮箱是最基础的有效标识符
展示模式见下方决策逻辑取决于支付时是否涉及浏览器交互
生产环境 vs 沙箱环境沙箱SDK URL(直到用户确认)生产环境SDK URL尚未公开
展示模式:如果支付时消费者正在与浏览器交互(且页面可加载
inflow.js
),则使用
FULL
模式。其他场景(服务器间调用、自主Agent、定时任务、AI Worker)均使用
HEADLESS
模式。
Webhook vs 轮询:生产环境使用Webhook。仅在本地开发、脚本或无公开HTTPS接口的场景下使用轮询。
用户身份识别:支付请求必须指向已知的
userId
。Agent需执行以下操作之一:
  • 从用户处获取
    userId
    (如果用户已知),或者
  • 通过
    POST /v1/users/search
    接口按邮箱/手机号/用户名搜索,或者
  • 如果搜索返回404,则注册新消费者
必须先完成用户身份识别,再创建支付请求。

🛑 Hard Backend Gate (Read Before ANY Code)

🛑 严格后端限制(编写代码前必读)

InFlow integration is always backend work. The
INFLOW_API_KEY
is a merchant key that must never reach a browser. There is no exception, demo mode, or "just for now" carve-out.
InFlow集成始终属于后端工作
INFLOW_API_KEY
是商户密钥,绝对不能暴露到浏览器端。没有例外、演示模式或"暂时处理"的余地。

Mandatory gate

强制限制

Before writing any InFlow HTTP call, you MUST be able to name the single server-only surface where
X-API-Key
will be set. Acceptable surfaces:
  • An Express / Fastify / Koa / Hono / Hapi route handler
  • A Next.js Route Handler (
    app/api/.../route.ts
    ) or Server Action — NOT a Client Component
  • A SvelteKit
    +server.ts
    /
    +page.server.ts
    — NOT a
    +page.svelte
    component
  • A Nuxt
    server/api/*.ts
    /
    server/routes/*.ts
    — NOT a Vue component or composable
  • A Remix
    loader
    /
    action
    — NOT a route component body
  • A serverless function: Vercel Function, Netlify Function, Cloudflare Worker, AWS Lambda, etc.
  • A Python / Go / Rust / .NET / Java HTTP service
  • A standalone backend script or worker (no browser involved)
If the project does NOT have any such surface — for example, a pure Vite + React SPA, a static site, a Storybook setup, a Chrome extension popup — STOP. Tell the user they need a backend surface for the API key, even if it's a single-file serverless function or a 30-line Express server. Offer to scaffold one. Do not proceed until the user agrees and the backend surface exists.
在编写任何InFlow HTTP调用之前,你必须能够明确指定仅在服务器端存储
X-API-Key
的位置
。允许的位置包括:
  • Express / Fastify / Koa / Hono / Hapi路由处理器
  • Next.js路由处理器(
    app/api/.../route.ts
    )或Server Action——不能是客户端组件
  • SvelteKit的
    +server.ts
    /
    +page.server.ts
    ——不能是
    +page.svelte
    组件
  • Nuxt的
    server/api/*.ts
    /
    server/routes/*.ts
    ——不能是Vue组件或组合式函数
  • Remix的
    loader
    /
    action
    ——不能是路由组件主体
  • 无服务器函数:Vercel Function、Netlify Function、Cloudflare Worker、AWS Lambda等
  • Python / Go / Rust / .NET / Java HTTP服务
  • 独立后端脚本或Worker(不涉及浏览器)
如果项目没有上述任何服务器端环境——例如纯Vite + React单页应用、静态站点、Storybook环境、Chrome扩展弹窗——立即停止。告知用户需要为API密钥准备后端环境,哪怕是一个单文件无服务器函数或30行的Express服务器。在用户同意并搭建好后端环境之前,不要继续操作。

Framework-specific footguns (key-leak mechanics)

框架常见陷阱(密钥泄露途径)

These are real, common ways merchant keys end up in browsers. Refuse them all.
Next.js:
  • process.env.INFLOW_API_KEY
    referenced inside a
    'use client'
    component is
    undefined
    at runtime — Next.js does NOT bundle non-
    NEXT_PUBLIC_
    env vars into client code. The bug surfaces as a broken request with no auth header.
  • The disaster happens when an agent "fixes" this
    undefined
    by renaming the var to
    NEXT_PUBLIC_INFLOW_API_KEY
    . That prefix tells the bundler to inline the value into the client bundle → the key is now shipped to every visitor.
  • NEVER prefix
    INFLOW_API_KEY
    with
    NEXT_PUBLIC_
    .
    The correct fix is to move the call to a Server Component, Route Handler (
    app/api/.../route.ts
    ), or Server Action — never to make the key public.
Vite (React, Vue, Svelte, Solid, etc.):
  • import.meta.env.INFLOW_API_KEY
    is
    undefined
    unless prefixed with
    VITE_
    . Some agents "fix" this by renaming to
    VITE_INFLOW_API_KEY
    Vite then inlines the key into the client bundle and ships it to every visitor.
  • NEVER prefix
    INFLOW_API_KEY
    with
    VITE_
    .
    Vite has no server runtime by itself. If you need a backend, the user has to add one (Express, Fastify, or pair with a serverless function).
SvelteKit:
  • Components and
    +page.svelte
    files run in the browser. Use
    +server.ts
    ,
    +page.server.ts
    , or
    $lib/server/*
    (the
    server
    directory is enforced as server-only by the bundler).
Nuxt:
  • Vue components and composables run in the browser. Use
    server/api/*
    route handlers or
    server/utils/*
    . Never read
    INFLOW_API_KEY
    from a
    <script setup>
    block in a
    .vue
    file.
Astro / Remix / Qwik / Solid Start:
  • All have a server/client split. The rule is the same:
    INFLOW_API_KEY
    is read only inside the framework's server boundary (loader, action, server route, etc.), never in component code.
以下是商户密钥泄露到浏览器的真实常见场景,需全部拒绝:
Next.js:
  • 'use client'
    组件中引用
    process.env.INFLOW_API_KEY
    会在运行时返回
    undefined
    ——Next.js不会将非
    NEXT_PUBLIC_
    前缀的环境变量打包到客户端代码中。该问题表现为请求因缺少授权头而失败。
  • 当Agent试图通过将变量重命名为
    NEXT_PUBLIC_INFLOW_API_KEY
    来"修复"
    undefined
    问题时,灾难就会发生。该前缀会告诉打包工具将值内联到客户端代码中→ 密钥会被发送给每一位访客
  • 绝对不要给
    INFLOW_API_KEY
    添加
    NEXT_PUBLIC_
    前缀
    。正确的修复方式是将调用移至Server Component、路由处理器(
    app/api/.../route.ts
    )或Server Action——绝对不要将密钥公开。
Vite(React、Vue、Svelte、Solid等):
  • import.meta.env.INFLOW_API_KEY
    仅在添加
    VITE_
    前缀时才会生效。部分Agent会通过重命名为
    VITE_INFLOW_API_KEY
    来"修复"问题→ Vite会将密钥内联到客户端代码中并发送给每一位访客
  • 绝对不要给
    INFLOW_API_KEY
    添加
    VITE_
    前缀
    。Vite本身没有服务器运行时。如果需要后端,用户必须添加一个(Express、Fastify或搭配无服务器函数)。
SvelteKit:
  • 组件和
    +page.svelte
    文件在浏览器中运行。请使用
    +server.ts
    +page.server.ts
    $lib/server/*
    (打包工具会强制
    server
    目录仅在服务器端运行)。
Nuxt:
  • Vue组件和组合式函数在浏览器中运行。请使用
    server/api/*
    路由处理器或
    server/utils/*
    。绝对不要在
    .vue
    文件的
    <script setup>
    块中读取
    INFLOW_API_KEY
Astro / Remix / Qwik / Solid Start:
  • 所有框架都有服务器/客户端拆分规则。规则一致:
    INFLOW_API_KEY
    仅能在框架的服务器边界内读取(loader、action、服务器路由等),绝对不能在组件代码中读取。

The "just a demo" trap

"只是演示"的陷阱

If the user says "this is just a demo", "just for testing", "we'll add the backend later", "just for now", or anything similar — the rule still applies. Embedded merchant keys leak via:
  • Git history (especially after a
    git push --force
    "fix")
  • Browser devtools (anyone can open them)
  • Browser extensions (they read your scripts)
  • Error reports / crash dumps shared in tickets
  • Screenshots posted in Slack / GitHub / Stack Overflow
  • Build artifacts uploaded to CDNs
  • Deployed preview URLs that get crawled
A "temporary" key in the browser is a permanent key on the internet. There is no safe short-term embed.
For a quick demo, the right move is still a backend — just a small one. A 20-line Express script, a single Vercel Function, or a Cloudflare Worker. All take less time than the apology email after a key leak.

如果用户说*"这只是演示""只是测试用""我们之后再添加后端""暂时先用着"*或类似话语——规则仍然适用。嵌入到浏览器中的商户密钥会通过以下途径泄露:
  • Git历史(尤其是
    git push --force
    "修复"之后)
  • 浏览器开发者工具(任何人都能打开)
  • 浏览器扩展(会读取脚本内容)
  • 工单中分享的错误报告/崩溃日志
  • Slack / GitHub / Stack Overflow中发布的截图
  • 上传到CDN的构建产物
  • 被爬虫抓取的预览部署URL
浏览器中的"临时"密钥会永久暴露在互联网上。没有安全的短期嵌入方式。
对于快速演示,正确的做法仍然是搭建后端——只是一个小型后端即可。20行的Express脚本、单个Vercel Function或Cloudflare Worker都可以。搭建这些的时间比密钥泄露后的道歉邮件要短得多。

Backend-Only Fast Path (No Browser, No Frontend)

纯后端快速路径(无浏览器、无前端)

If the user is integrating InFlow into a pure backend, API, agent, worker, or service — skip the browser/SDK material entirely and follow this short path. This is the most common backend integration shape.
Order of operations:
  1. Set env vars (Step 1) —
    INFLOW_API_KEY
    , optionally
    INFLOW_WEBHOOK_SECRET
  2. Identify the consumer (Step 2) —
    POST /v1/users/search
    with email/mobile/username → get
    userId
  3. Create the payment with
    display: HEADLESS
    (Step 3) — pass
    policyId
    if you have one for 0-click; otherwise the consumer gets a mobile/email approval prompt
  4. Persist your correlation key — store
    { yourOrderId, requestId, transactionId? }
    so the webhook handler can look up the right order. The webhook payload only carries InFlow's IDs; you must map them back to your business object.
  5. Wait for confirmation — either:
    • Webhook handler (Step 5) — recommended for production. Verify HMAC, idempotency by
      eventId
      , fulfill on
      event.data.status === "PAID"
      (with the discriminator check)
    • OR polling (Step 6) — for scripts, batch jobs, local dev. Call
      GET /v1/requests/{requestId}
      until status leaves
      PENDING
Skip: Step 4 (frontend SDK), the full-stack happy path diagram below, anything mentioning
inflow.js
or browsers.
For autonomous agents that need 0-click headless payments without consumer interaction per transaction, also implement Step 7 (Policies) and attach
policyId
to every payment request.

如果用户要将InFlow集成到纯后端、API、Agent、Worker或服务中——完全跳过浏览器/SDK相关内容,遵循以下简化流程。这是最常见的后端集成模式。
操作顺序:
  1. 设置环境变量(步骤1)——
    INFLOW_API_KEY
    ,可选
    INFLOW_WEBHOOK_SECRET
  2. 识别消费者(步骤2)——调用
    POST /v1/users/search
    接口,传入邮箱/手机号/用户名→获取
    userId
  3. 创建
    display: HEADLESS
    模式的支付请求
    (步骤3)——如果有实现一键式支付的策略,传入
    policyId
    ;否则消费者会收到手机/邮箱授权提示
  4. 持久化关联密钥——存储
    { yourOrderId, requestId, transactionId? }
    ,以便Webhook处理器能找到对应的订单。Webhook payload仅包含InFlow的ID;你必须将其映射回业务对象。
  5. 等待确认——选择以下方式之一:
    • Webhook处理器(步骤5)——生产环境推荐。验证HMAC签名,通过
      eventId
      实现幂等性,当
      event.data.status === "PAID"
      时完成订单(需进行判别检查)
    • 或轮询(步骤6)——适用于脚本、批量任务、本地开发。调用
      GET /v1/requests/{requestId}
      直到状态不再是
      PENDING
跳过内容:步骤4(前端SDK)、下方的全栈流程示意图、所有提及
inflow.js
或浏览器的内容。
对于自主Agent:如果需要无需每次交易都经过消费者交互的一键式无界面支付,还需实现步骤7(策略),并将
policyId
附加到每个支付请求中。

Order Correlation (Backend Pattern)

订单关联(后端模式)

A common bug in payment integrations: the webhook arrives but the handler can't figure out which of your orders it belongs to. The InFlow webhook payload contains InFlow's IDs (
requestId
,
transactionId
,
approvalId
) but none of your business IDs. You must store the mapping yourself at payment creation time.
Minimum mapping table (in your DB / store / cache):
Your columnSource
your_order_id
Your business object — the thing being purchased
inflow_request_id
Returned from
POST /v1/requests/payment
inflow_transaction_id
Returned later in webhook payload (
event.data.transactionId
) — start NULL, fill on first event
status
Your local state machine:
pending
/
paid
/
failed
created_at
Audit trail
At payment creation: insert a row with
your_order_id
+
inflow_request_id
, status
pending
.
At webhook receipt: look up the row by
inflow_request_id
(use
event.data.approvalId
or correlate via
requestId
if
data
is an
ApprovalResponse
; use
event.data.transactionId
once it appears in a
TransactionResponse
). Update status. Fulfill the order.
Idempotency: also store processed
event.eventId
s separately so you don't double-fulfill on retries (InFlow may resend the same event for up to 72 hours).

支付集成中的常见问题:Webhook到达后,处理器无法确定对应的订单。InFlow Webhook payload包含InFlow的ID(
requestId
transactionId
approvalId
),但不包含任何业务ID。你必须在创建支付请求时自行存储映射关系。
最小映射表(存储在你的数据库/存储/缓存中):
你的字段来源
your_order_id
你的业务对象——即购买的商品/服务
inflow_request_id
POST /v1/requests/payment
接口返回的ID
inflow_transaction_id
后续Webhook payload中的
event.data.transactionId
——初始值为NULL,首次收到事件时填充
status
你的本地状态机:
pending
/
paid
/
failed
created_at
审计日志
创建支付请求时:插入一条包含
your_order_id
+
inflow_request_id
的记录,状态设为
pending
收到Webhook时:通过
inflow_request_id
查找记录(如果
data
ApprovalResponse
,则使用
event.data.approvalId
或通过
requestId
关联;如果是
TransactionResponse
,则使用
event.data.transactionId
)。更新状态并完成订单。
幂等性:还需单独存储已处理的
event.eventId
,避免因重试导致重复完成订单(InFlow可能在72小时内重发同一事件)。

Happy Path: Add Checkout to a Web App

常见场景:为Web应用添加结账功能

This is the most common request. End-to-end recipe for "I want to accept InFlow payments at checkout":
┌──────────────────────────────────────────────────────────────────┐
│ Frontend (browser)                                               │
├──────────────────────────────────────────────────────────────────┤
│ 1. Collect consumer email at checkout                            │
│ 2. Click "Pay with InFlow" → POST to YOUR /api/inflow/checkout   │
│ 3. Receive { requestId } from your backend                       │
│ 4. Render popup: new InFlow.Request(requestId).render({...})     │
│ 5. statusCallback fires when consumer approves                   │
│ 6. Show "processing..." (DO NOT fulfill yet)                     │
└──────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────┐
│ Backend (your /api/inflow/checkout endpoint)                     │
├──────────────────────────────────────────────────────────────────┤
│ 1. Receive { email, amount } from frontend                       │
│ 2. POST /v1/users/search { email } → get userId                  │
│    (if 404 → POST /v1/requests/register first)                   │
│ 3. POST /v1/requests/payment {                                   │
│      userId, amount, currency: "USDC",                           │
│      display: "FULL", userDetails: ["EMAIL"]                     │
│    }                                                             │
│ 4. Return { requestId } to frontend                              │
└──────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────┐
│ Backend (your /api/webhooks/inflow endpoint)                     │
├──────────────────────────────────────────────────────────────────┤
│ 1. Receive POST from InFlow with x-inflow-signature header       │
│ 2. Verify HMAC-SHA256 signature → 401 if invalid                 │
│ 3. Check eventId against idempotency store → skip if seen        │
│ 4. Fulfill ONLY if ALL three are true:                           │
│    a. event.type === "TRANSACTION_UPDATED"                       │
│    b. event.data.transactionId is present                        │
│       (i.e. data is a TransactionResponse, not Approval)         │
│    c. event.data.status === "PAID"                               │
│    (use INNER data.status, NOT outer event.status)               │
│ 5. Return HTTP 200                                               │
└──────────────────────────────────────────────────────────────────┘
Two
status
fields, two meanings.
The webhook envelope has an outer
event.status
(
DELIVERED
/
FAILED
/
PENDING
/
DISABLED
— InFlow's delivery state) and the inner
event.data.status
(the actual transaction state like
PAID
/
PENDING
/
INSUFFICIENT_FUNDS
). Always check
event.data.status
for fulfillment decisions.
See Step 5 for details.
The three things the user MUST do manually:
  1. Get an
    INFLOW_API_KEY
    (from InFlow dashboard, or create an agentic user — see "Agentic User Setup" below)
  2. Register the webhook URL
    https://yourdomain.com/api/webhooks/inflow
    in the InFlow dashboard (no API for this)
  3. Get the
    INFLOW_WEBHOOK_SECRET
    from the InFlow dashboard after registering the URL
After implementing the integration, summarize these manual steps prominently.

这是最常见的需求。以下是"我想在结账时接收InFlow支付"的端到端实现方案:
┌──────────────────────────────────────────────────────────────────┐
│ 前端(浏览器)                                               │
├──────────────────────────────────────────────────────────────────┤
│ 1. 在结账页面收集消费者邮箱                            │
│ 2. 点击"使用InFlow支付" → 向你的/api/inflow/checkout发起POST请求   │
│ 3. 从后端接收{ requestId }                       │
│ 4. 渲染弹窗:new InFlow.Request(requestId).render({...})     │
│ 5. 消费者完成授权后触发statusCallback                   │
│ 6. 显示"处理中..."(**不要立即完成订单**)                     │
└──────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────┐
│ 后端(你的/api/inflow/checkout接口)                     │
├──────────────────────────────────────────────────────────────────┤
│ 1. 从前端接收{ email, amount }                       │
│ 2. 调用POST /v1/users/search { email } → 获取userId                  │
│   (如果返回404 → 先调用POST /v1/requests/register)                   │
│ 3. 调用POST /v1/requests/payment {                                   │
│      userId, amount, currency: "USDC",                           │
│      display: "FULL", userDetails: ["EMAIL"]                     │
│    }                                                             │
│ 4. 将{ requestId }返回给前端                              │
└──────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────┐
│ 后端(你的/api/webhooks/inflow接口)                     │
├──────────────────────────────────────────────────────────────────┤
│ 1. 接收InFlow的POST请求,包含x-inflow-signature请求头       │
│ 2. 验证HMAC-SHA256签名 → 无效则返回401                 │
│ 3. 检查eventId是否在幂等性存储中 → 已处理则跳过        │
│ 4. 仅当以下三个条件全部满足时才完成订单:                           │
│    a. event.type === "TRANSACTION_UPDATED"                       │
│    b. event.data.transactionId存在                        │
│      (即data是TransactionResponse,而非Approval)         │
│    c. event.data.status === "PAID"                               │
│   (使用内层的data.status,而非外层的event.status)               │
│ 5. 返回HTTP 200                                               │
└──────────────────────────────────────────────────────────────────┘
两个
status
字段,两种含义
。Webhook信封包含外层
event.status
DELIVERED
/
FAILED
/
PENDING
/
DISABLED
——InFlow的投递状态)和内层
event.data.status
(实际交易状态,如
PAID
/
PENDING
/
INSUFFICIENT_FUNDS
)。必须检查
event.data.status
来决定是否完成订单
。详见步骤5。
用户必须手动完成的三件事:
  1. 获取
    INFLOW_API_KEY
    (从InFlow控制台,或创建Agent用户——见下方"Agent用户设置")
  2. 在InFlow控制台中注册Webhook URL
    https://yourdomain.com/api/webhooks/inflow
    (无API支持此操作)
  3. 注册URL后,从InFlow控制台获取
    INFLOW_WEBHOOK_SECRET
完成集成后,请突出总结这些手动步骤。

Reference Material

参考资料

This skill ships with three reference documents in
references/
. Read them when you need details beyond what's in this file:
  • references/overview.md
    — Platform concepts, approval model, FULL vs HEADLESS display modes
  • references/flows.md
    — 8 worked-out integration flows with HTTP examples
  • references/api-reference.md
    — Every endpoint, every schema, every enum value
When the user asks something specific (e.g. "how do I list policies?", "what does the webhook payload look like?"), look it up in the references rather than guessing.

本技能附带三份参考文档,位于
references/
目录下。当需要本文件之外的详细信息时,请查阅这些文档:
  • references/overview.md
    ——平台概念、授权模型、FULL与HEADLESS展示模式
  • references/flows.md
    ——8个完整的集成流程示例及HTTP调用示例
  • references/api-reference.md
    ——所有接口、所有Schema、所有枚举值
当用户询问特定问题(例如"如何列出策略?"、"Webhook payload是什么样的?")时,请查阅参考资料,不要猜测。

Implementation Guide

实现指南

Prerequisites

前置条件

Before writing any code:
  1. Read the project — detect language and framework via
    package.json
    ,
    requirements.txt
    ,
    go.mod
    ,
    Cargo.toml
    , etc. Use whatever HTTP client and web framework already exists. Do not impose Express, Next.js, React, or any specific framework.
  2. Pass the Hard Backend Gate (see the "🛑 Hard Backend Gate" section earlier in this file) — name the server-only surface where the API key will live. If there isn't one, scaffold one (with the user's agreement) before continuing.
  3. Confirm the user has an API key — if not, walk them through the agentic user setup at the bottom of this file.
  4. Confirm the project type — pure backend / fullstack web app / CLI / agent / worker. Pure-frontend projects are NOT a valid type — see the Hard Backend Gate.
    • Pure backend → Server payment + webhook (or polling)
    • Fullstack / web app → Server payment + frontend SDK + webhook
    • CLI / agent / worker → Headless payment + webhook (or polling) + maybe policies for 0-click
编写代码之前:
  1. 了解项目——通过
    package.json
    requirements.txt
    go.mod
    Cargo.toml
    等文件检测语言和框架。使用项目已有的HTTP客户端和Web框架,不要强制使用Express、Next.js、React或其他特定框架。
  2. 通过严格后端限制检查(见前文「🛑 严格后端限制」章节)——明确指定存储API密钥的服务器端位置。如果没有,需在用户同意后搭建一个,再继续操作。
  3. 确认用户已有API密钥——如果没有,引导用户完成下方的Agent用户设置。
  4. 确认项目类型——纯后端/全栈Web应用/CLI/Agent/Worker。纯前端项目不属于有效类型——见严格后端限制。
    • 纯后端 → 服务器支付 + Webhook(或轮询)
    • 全栈/Web应用 → 服务器支付 + 前端SDK + Webhook
    • CLI/Agent/Worker → 无界面支付 + Webhook(或轮询) + 可能需要策略实现一键式支付

Step 1: Set Up Environment Variables

步骤1:设置环境变量

Add these to the project's existing env file (
.env
,
.env.local
, etc. — match the project's convention). Add to
.env.example
too.
undefined
将以下变量添加到项目现有的环境文件中(
.env
.env.local
等——遵循项目约定)。同时添加到
.env.example
中。
undefined

Required for any InFlow integration

任何InFlow集成都需要

INFLOW_API_KEY=<your-private-key>
INFLOW_API_KEY=<你的私有密钥>

Required if you handle webhooks

处理Webhook时需要

INFLOW_WEBHOOK_SECRET=<your-webhook-secret>
INFLOW_WEBHOOK_SECRET=<你的Webhook密钥>

Optional: defaults to https://api.inflowpay.ai

可选:默认值为https://api.inflowpay.ai

INFLOW_API_BASE_URL=https://api.inflowpay.ai

INFLOW_API_BASE_URL=https://api.inflowpay.ai


Make sure `.env` and `.env.local` are in `.gitignore`.

**Critical:** `INFLOW_API_KEY` is server-side only. Never embed it in frontend code, never expose it via a public route, never log it.

确保`.env`和`.env.local`已添加到`.gitignore`中。

**关键提示**:`INFLOW_API_KEY`仅能在服务器端使用。绝对不要嵌入到前端代码中,不要通过公开路由暴露,不要记录日志。

Step 2: Find or Create the Consumer

步骤2:查找或创建消费者

A payment must be addressed to a
userId
. If the user doesn't know the consumer's
userId
, you must look it up first.
Search by email (or mobile / username):
http
POST https://api.inflowpay.ai/v1/users/search
X-API-Key: <INFLOW_API_KEY>
Content-Type: application/json

{ "email": "alice@example.com" }
On match (200):
json
{ "userId": "8a5c2948-9e08-439e-a7a6-9f0ee335f566" }
On no match (404): initiate registration:
http
POST https://api.inflowpay.ai/v1/requests/register
X-API-Key: <INFLOW_API_KEY>
Content-Type: application/json

{
  "display": "FULL",
  "userDetails": ["EMAIL", "NAME"]
}
⚠️ API quirk:
RegisterRequest
lists
userId
as optional (only
display
and
userDetails
are required per the OpenAPI spec), but its semantics for a true new-user registration flow are not documented. Do not invent a UUID. Try the call without
userId
first; if the API rejects it, the user must obtain a provisional
userId
from InFlow support or check vendor docs. Treat this as vendor-ambiguous and surface the ambiguity to the user — do not silently work around it.
This returns an
ApprovalResponse
with a
requestId
. Hand the
requestId
to the frontend SDK to render the registration popup. After approval, the consumer is registered and can be searched again.
Search field rules:
  • email
    — RFC email format
  • mobile
    — E.164 format like
    +15551234567
  • username
    — 3-16 chars, alphanumeric +
    -_
userDetails
enum values:
BIRTHDATE
,
DEPOSIT_ADDRESSES
,
EMAIL
,
MOBILE
,
NAME
,
NATIONAL_ID
,
PHYSICAL_ADDRESS
,
USERNAME
. Always include at least
EMAIL
.
支付请求必须指向
userId
。如果用户不知道消费者的
userId
,必须先进行查找。
按邮箱(或手机号/用户名)搜索:
http
POST https://api.inflowpay.ai/v1/users/search
X-API-Key: <INFLOW_API_KEY>
Content-Type: application/json

{ "email": "alice@example.com" }
匹配成功(200):
json
{ "userId": "8a5c2948-9e08-439e-a7a6-9f0ee335f566" }
匹配失败(404): 发起注册请求:
http
POST https://api.inflowpay.ai/v1/requests/register
X-API-Key: <INFLOW_API_KEY>
Content-Type: application/json

{
  "display": "FULL",
  "userDetails": ["EMAIL", "NAME"]
}
⚠️ API特性
RegisterRequest
userId
列为可选(根据OpenAPI规范,仅
display
userDetails
为必填),但针对新用户注册流程的语义尚未文档化。不要自行生成UUID。先尝试不传入
userId
调用接口;如果API拒绝,用户必须从InFlow支持团队获取临时
userId
或查阅厂商文档。请将此不确定性告知用户——不要默默变通。
该接口会返回包含
requestId
ApprovalResponse
。将
requestId
传递给前端SDK以渲染注册弹窗。消费者完成授权后,即可再次搜索到该用户。
搜索字段规则:
  • email
    ——符合RFC标准的邮箱格式
  • mobile
    ——E.164格式,如
    +15551234567
  • username
    ——3-16字符,仅包含字母、数字及
    -_
userDetails
枚举值:
BIRTHDATE
DEPOSIT_ADDRESSES
EMAIL
MOBILE
NAME
NATIONAL_ID
PHYSICAL_ADDRESS
USERNAME
。至少包含
EMAIL

Step 3: Create a Payment Request

步骤3:创建支付请求

http
POST https://api.inflowpay.ai/v1/requests/payment
X-API-Key: <INFLOW_API_KEY>
Content-Type: application/json

{
  "userId": "<consumer-uuid-from-Step-2>",
  "amount": 99.99,
  "currency": "USDC",
  "display": "FULL",
  "userDetails": ["EMAIL"]
}
Response (200):
json
{
  "requestId": "<uuid>",
  "type": "PAYMENT",
  "status": "PENDING"
}
Required fields:
amount
(≥ 0.01),
currency
,
display
,
userDetails
. Optional:
userId
,
policyId
(for auto-approval via a pre-existing policy).
Amount format: Plain decimal in major units of the chosen stablecoin.
99.99
means 99.99 USDC (≈ $99.99). No cents/minor units.
Adapt the HTTP call to the user's stack — read the project and use whatever HTTP client is already in use. The exact code shape depends on the language (Node fetch, Python requests, Go net/http, etc.).
Error handling:
  • 4xx → user error (missing field, invalid format, user not found). Return the error to the caller, don't retry.
  • 5xx / network → transient. Retry with exponential backoff (max 3 attempts) before failing the checkout.
After getting
requestId
, the next step depends on the display mode:
  • FULL
    → hand
    requestId
    to the frontend SDK (Step 4)
  • HEADLESS
    → wait for the webhook (Step 5) or poll
    GET /v1/requests/{requestId}
    (Step 6)
http
POST https://api.inflowpay.ai/v1/requests/payment
X-API-Key: <INFLOW_API_KEY>
Content-Type: application/json

{
  "userId": "<步骤2获取的消费者UUID>",
  "amount": 99.99,
  "currency": "USDC",
  "display": "FULL",
  "userDetails": ["EMAIL"]
}
响应(200):
json
{
  "requestId": "<UUID>",
  "type": "PAYMENT",
  "status": "PENDING"
}
必填字段:
amount
(≥ 0.01)、
currency
display
userDetails
可选字段:
userId
policyId
(用于通过已有策略自动授权)。
金额格式: 所选稳定币的主单位十进制数。
99.99
表示99.99 USDC(≈ 99.99美元)。不支持分/辅单位。
适配用户技术栈——了解项目并使用已有的HTTP客户端。具体代码形式取决于语言(Node fetch、Python requests、Go net/http等)。
错误处理:
  • 4xx → 用户错误(缺失字段、格式无效、用户不存在)。将错误返回给调用方,不要重试。
  • 5xx / 网络错误 → 临时错误。使用指数退避重试(最多3次)后再终止结账流程。
获取
requestId
后,下一步取决于展示模式:
  • FULL
    → 将
    requestId
    传递给前端SDK(步骤4)
  • HEADLESS
    → 等待Webhook(步骤5)或轮询
    GET /v1/requests/{requestId}
    (步骤6)

Step 4: Frontend SDK (
inflow.js
) — for
display: FULL

步骤4:前端SDK(
inflow.js
)——适用于
display: FULL
模式

For browser-based flows. The frontend loads
inflow.js
, gets a
requestId
from the backend, and renders a popup.
Load the SDK:
html
<script src="https://sandbox.inflowpay.ai/sdk/inflow.js"></script>
⚠️ This is the sandbox URL. The production URL is not yet publicly documented. Before deploying to production, the user should confirm the production SDK URL with InFlow.
Render a request:
js
// Get the requestId from your backend (which calls POST /v1/requests/{login|register|payment|...})
const requestId = await fetchRequestIdFromYourBackend();

const request = new InFlow.Request(requestId);
request.render({
  statusCallback: (status) => {
    // status.status is one of: PENDING, APPROVED, DECLINED, EXPIRED, CANCELLED
    if (status.status === InFlow.STATUS.APPROVED) {
      // SDK indicated user approved. Show "processing..."
      // DO NOT fulfill the order here — wait for the webhook.
    }
  }
});
Same pattern works for login, register, payment, and details requests. Only the backend endpoint that produces the
requestId
differs.
Adapt the surrounding markup to the user's framework — wrap in a React/Vue/Svelte component if needed. The SDK itself is framework-agnostic.
适用于基于浏览器的流程。前端加载
inflow.js
,从后端获取
requestId
并渲染弹窗。
加载SDK:
html
<script src="https://sandbox.inflowpay.ai/sdk/inflow.js"></script>
⚠️ 这是沙箱环境URL。生产环境URL尚未公开。部署到生产环境前,用户需与InFlow确认生产环境SDK URL。
渲染请求:
js
// 从你的后端获取requestId(后端调用POST /v1/requests/{login|register|payment|...}生成)
const requestId = await fetchRequestIdFromYourBackend();

const request = new InFlow.Request(requestId);
request.render({
  statusCallback: (status) => {
    // status.status可选值:PENDING, APPROVED, DECLINED, EXPIRED, CANCELLED
    if (status.status === InFlow.STATUS.APPROVED) {
      // SDK提示用户已授权。显示"处理中..."
      // **不要在此处完成订单**——等待Webhook通知。
    }
  }
});
相同模式适用于登录、注册、支付和详情请求。仅生成
requestId
的后端接口不同。
适配用户框架——如果需要,可封装到React/Vue/Svelte组件中。SDK本身与框架无关。

Step 5: Webhook Handler

步骤5:Webhook处理器

InFlow delivers signed events to your registered webhook URL.
🚨 CRITICAL MANUAL STEP: The webhook URL must be registered in the InFlow dashboard by the user — there is no API for this. After implementing the handler, tell the user explicitly:
"Go to the InFlow dashboard, navigate to webhooks, register
https://yourdomain.com/api/webhooks/inflow
(or your equivalent URL), and copy the webhook secret into your
INFLOW_WEBHOOK_SECRET
env var. Until you do this, no events will be delivered."
For local testing, suggest ngrok or similar to expose
localhost
over HTTPS.
Webhook payload shape — note the two
status
fields:
json
{
  "eventId": "<uuid>",
  "webhookId": "<uuid>",
  "type": "TRANSACTION_UPDATED",
  "status": "DELIVERED",          ← OUTER: InFlow's delivery state. Ignore for fulfillment.
  "data": {
    "transactionId": "<uuid>",
    "approvalId": "<uuid>",
    "amount": 99.99,
    "currency": "USDC",
    "blockchain": "SOLANA",
    "status": "PAID",              ← INNER: actual transaction state. Use this for fulfillment.
    "transactionHash": "0x...",
    "created": "2026-01-01T00:00:00.000Z"
  },
  "created": "2026-01-01T00:00:00.000Z",
  "delivered": "2026-01-01T00:00:01.000Z"
}
Critical disambiguation: the envelope has TWO
status
fields:
  • event.status
    (outer) — InFlow's webhook delivery state:
    PENDING
    /
    DELIVERED
    /
    FAILED
    /
    DISABLED
    . This tells you whether InFlow successfully delivered the event. Don't branch on this for business logic.
  • event.data.status
    (inner) — the actual transaction or approval state:
    PAID
    ,
    INITIATED
    ,
    PENDING
    ,
    INSUFFICIENT_FUNDS
    ,
    APPROVED
    ,
    DECLINED
    , etc. This is what you check for fulfillment.
Event types:
TRANSACTION_CREATED
,
TRANSACTION_UPDATED
The
data
field is a discriminated union:
  • If it has
    requestId
    and
    type
    (LOGIN/PAYMENT/etc.) → it's an
    ApprovalResponse
  • If it has
    transactionId
    ,
    blockchain
    , and
    transactionHash
    → it's a
    TransactionResponse
Only fulfill orders when ALL of these are true:
event.type === "TRANSACTION_UPDATED"
, the
data
field is shaped as a
TransactionResponse
(presence of
transactionId
is the simplest discriminator), and
event.data.status === "PAID"
. Do not generalize to "any TRANSACTION_* event with status PAID" — an
ApprovalResponse
payload may also have a
status
field (
APPROVED
/
PENDING
/etc.) and using it for fulfillment will crash or misfire.
data.status
values that matter (TransactionResponse):
  • PAID
    — payment completed (this is your "fulfill the order" signal)
  • INITIATED
    ,
    PENDING
    — in progress, do nothing
  • INSUFFICIENT_FUNDS
    ,
    GENERAL_ERROR
    — failed, notify user
  • REFUNDED
    ,
    RETURNED
    — money was returned
Mandatory: HMAC signature verification. Every webhook is signed with HMAC-SHA256. The signature is in the
x-inflow-signature
header. Verify it before processing.
js
// Generic — adapt to whatever crypto library the project uses.
// Node.js example using built-in crypto module:
import crypto from 'node:crypto';

function verifyInflowWebhook(rawBody, signatureHeader, secret) {
  // Guard against missing or malformed header — timingSafeEqual throws on length mismatch
  if (typeof signatureHeader !== 'string' || signatureHeader.length === 0) return false;
  if (!rawBody || !secret) return false;

  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  if (expected.length !== signatureHeader.length) return false;

  // Constant-time compare to prevent timing attacks
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signatureHeader),
  );
}
The same logic applies in any language: HMAC-SHA256 the raw request body using the webhook secret, hex-encode it, compare to the
x-inflow-signature
header.
Critical: verify against the raw request body bytes, NOT a re-serialized JSON object. If the framework parses JSON automatically, you must capture the raw body before parsing. The exact mechanism depends on the framework.
Webhook handler responsibilities:
  1. Verify the signature → reject with HTTP 401 if invalid
  2. Parse the JSON body
  3. Check
    eventId
    against an idempotency store → skip if already processed (InFlow may retry the same event for up to 72 hours)
  4. Look up your local order by
    event.data.approvalId
    or
    event.data.transactionId
    (see Order Correlation section above)
  5. Branch on the FULL three-condition rule (do not skip any of these):
    • Fulfill when ALL of:
      event.type === "TRANSACTION_UPDATED"
      AND
      event.data.transactionId
      is present (it's a
      TransactionResponse
      , not an
      ApprovalResponse
      ) AND
      event.data.status === "PAID"
    • Mark failed when
      event.data.transactionId
      present AND
      event.data.status
      is
      INSUFFICIENT_FUNDS
      /
      GENERAL_ERROR
      /
      RETURNED
    • Log only for other event types or non-terminal statuses (
      INITIATED
      ,
      PENDING
      , etc.)
    • NEVER fulfill on
      event.status
      (the OUTER delivery status) — that just means InFlow delivered the webhook successfully
  6. Return HTTP 200 to acknowledge
If the handler doesn't return 200, InFlow keeps retrying with exponential backoff for up to 72 hours.
InFlow会将签名后的事件发送到你注册的Webhook URL。
🚨 关键手动步骤:Webhook URL必须由用户在InFlow控制台中注册——无API支持此操作。完成处理器实现后,请明确告知用户:
"请登录InFlow控制台,进入Webhook页面,注册
https://yourdomain.com/api/webhooks/inflow
(或你的等效URL),并将Webhook密钥复制到
INFLOW_WEBHOOK_SECRET
环境变量中。完成此操作前,不会收到任何事件通知。"
本地测试时,建议使用ngrok或类似工具将
localhost
暴露为HTTPS地址。
Webhook payload结构——注意两个
status
字段:
json
{
  "eventId": "<UUID>",
  "webhookId": "<UUID>",
  "type": "TRANSACTION_UPDATED",
  "status": "DELIVERED",          ← 外层:InFlow的投递状态。不要用于业务逻辑判断。
  "data": {
    "transactionId": "<UUID>",
    "approvalId": "<UUID>",
    "amount": 99.99,
    "currency": "USDC",
    "blockchain": "SOLANA",
    "status": "PAID",              ← 内层:实际交易状态。用于判断是否完成订单。
    "transactionHash": "0x...",
    "created": "2026-01-01T00:00:00.000Z"
  },
  "created": "2026-01-01T00:00:00.000Z",
  "delivered": "2026-01-01T00:00:01.000Z"
}
关键区分:信封包含两个
status
字段:
  • event.status
    (外层)——InFlow的Webhook投递状态:
    PENDING
    /
    DELIVERED
    /
    FAILED
    /
    DISABLED
    。仅表示InFlow是否成功投递事件,不要用于业务逻辑分支判断。
  • event.data.status
    (内层)——实际交易或授权状态:
    PAID
    INITIATED
    PENDING
    INSUFFICIENT_FUNDS
    APPROVED
    DECLINED
    等。这是判断是否完成订单的依据
事件类型:
TRANSACTION_CREATED
TRANSACTION_UPDATED
data
字段是判别联合类型:
  • 如果包含
    requestId
    type
    (LOGIN/PAYMENT等)→ 是
    ApprovalResponse
  • 如果包含
    transactionId
    blockchain
    transactionHash
    → 是
    TransactionResponse
仅当以下所有条件满足时才完成订单
event.type === "TRANSACTION_UPDATED"
data
字段为
TransactionResponse
格式(最简单的判别方式是存在
transactionId
),且
event.data.status === "PAID"
。不要泛化为"任何TRANSACTION_*事件且状态为PAID"——
ApprovalResponse
payload也可能包含
status
字段(
APPROVED
/
PENDING
等),误用会导致崩溃或误触发。
data.status
关键值(TransactionResponse):
  • PAID
    ——支付完成(这是"完成订单"的信号)
  • INITIATED
    PENDING
    ——处理中,无需操作
  • INSUFFICIENT_FUNDS
    GENERAL_ERROR
    ——支付失败,通知用户
  • REFUNDED
    RETURNED
    ——款项已退回
强制要求:HMAC签名验证。每个Webhook都会使用HMAC-SHA256签名。签名位于
x-inflow-signature
请求头中。处理前必须验证签名。
js
// 通用逻辑——适配项目使用的加密库。
// Node.js示例,使用内置crypto模块:
import crypto from 'node:crypto';

function verifyInflowWebhook(rawBody, signatureHeader, secret) {
  // 防范缺失或格式错误的请求头——timingSafeEqual在长度不匹配时会抛出异常
  if (typeof signatureHeader !== 'string' || signatureHeader.length === 0) return false;
  if (!rawBody || !secret) return false;

  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  if (expected.length !== signatureHeader.length) return false;

  // 常量时间比较,防止时序攻击
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signatureHeader),
  );
}
任何语言的逻辑一致:使用Webhook密钥对原始请求体进行HMAC-SHA256加密,转为十六进制,与
x-inflow-signature
请求头的值对比。
关键提示:必须针对原始请求体字节进行验证,而非重新序列化的JSON对象。如果框架自动解析JSON,必须在解析前捕获原始请求体。具体实现方式取决于框架。
Webhook处理器职责:
  1. 验证签名 → 无效则返回HTTP 401
  2. 解析JSON请求体
  3. 检查
    eventId
    是否在幂等性存储中 → 已处理则跳过(InFlow可能在72小时内重发同一事件)
  4. 通过
    event.data.approvalId
    event.data.transactionId
    查找本地订单(见前文「订单关联」章节)
  5. 严格遵循三个条件进行分支判断(不要跳过任何一个):
    • 完成订单:当
      event.type === "TRANSACTION_UPDATED"
      event.data.transactionId
      存在(是
      TransactionResponse
      而非
      ApprovalResponse
      )且
      event.data.status === "PAID"
    • 标记为失败:当
      event.data.transactionId
      存在且
      event.data.status
      INSUFFICIENT_FUNDS
      /
      GENERAL_ERROR
      /
      RETURNED
    • 仅记录日志:其他事件类型或非终态状态(
      INITIATED
      PENDING
      等)
    • 绝对不要根据
      event.status
      (外层投递状态)完成订单
      ——仅表示InFlow成功投递了Webhook
  6. 返回HTTP 200确认接收
如果处理器未返回200,InFlow会使用指数退避重试,最多持续72小时。

Step 6: Polling (Webhook Alternative)

步骤6:轮询(Webhook替代方案)

If a webhook listener isn't possible (local dev, batch jobs, simple scripts), poll request status:
http
GET https://api.inflowpay.ai/v1/requests/{requestId}
X-API-Key: <INFLOW_API_KEY>
Returns the current
ApprovalResponse
. Poll every few seconds until
status
is no longer
PENDING
.
For full transaction details after approval:
http
GET https://api.inflowpay.ai/v1/transactions/{transactionId}
Where
transactionId
comes from the approved
ApprovalResponse.transactionId
.
如果无法使用Webhook监听器(本地开发、批量任务、简单脚本),可轮询请求状态:
http
GET https://api.inflowpay.ai/v1/requests/{requestId}
X-API-Key: <INFLOW_API_KEY>
返回当前的
ApprovalResponse
。每隔几秒轮询一次,直到
status
不再是
PENDING
授权完成后,如需完整交易详情:
http
GET https://api.inflowpay.ai/v1/transactions/{transactionId}
其中
transactionId
来自已授权的
ApprovalResponse.transactionId

Step 7: Policies (Enable 0-Click Headless Payments)

步骤7:策略(实现一键式无界面支付)

Policies are pre-approved spending rules. When a
HEADLESS
payment request includes a
policyId
and the request is within the policy's limits, it auto-approves immediately — no consumer interaction needed.
Use policies for:
  • Autonomous AI agents that need to make purchases without per-transaction approval
  • Recurring payments (subscriptions, automated top-ups)
  • High-frequency low-value transactions
Direct creation (seller-initiated):
http
POST https://api.inflowpay.ai/v1/policies
X-API-Key: <INFLOW_API_KEY>
Content-Type: application/json

{
  "type": "PAYMENT",
  "action": "APPROVE",
  "currency": "USDC",
  "budget": 1000,
  "threshold": 100,
  "period": "MONTHLY",
  "expires": "2026-12-31T00:00:00.000Z"
}
Consumer-initiated request (the consumer must approve the policy via their device):
http
POST https://api.inflowpay.ai/v1/requests/policy
X-API-Key: <INFLOW_API_KEY>
Content-Type: application/json

{
  "userId": "<consumer-uuid>",
  "display": "FULL",
  "userDetails": ["EMAIL"]
}
This returns an
ApprovalResponse
— same flow as login/payment. Consumer approves via SDK or mobile, and the policy becomes active.
Policy fields:
  • action
    APPROVE
    ,
    DECLINE
    ,
    REVIEW
  • period
    DAILY
    ,
    MONTHLY
    ,
    YEARLY
    ,
    ONCE
  • budget
    — total allowance for the period
  • threshold
    — per-transaction maximum
  • spent
    — current usage (read-only on
    PolicyResponse
    )
CRUD:
http
GET    /v1/policies
GET    /v1/policies/{policyId}
POST   /v1/policies
PUT    /v1/policies/{policyId}
DELETE /v1/policies/{policyId}/delete
Attach a policy to a payment:
json
{
  "userId": "<consumer-uuid>",
  "amount": 50,
  "currency": "USDC",
  "display": "HEADLESS",
  "userDetails": ["EMAIL"],
  "policyId": "<policy-uuid>"
}
If the request is within budget and threshold, the response will already show
status: APPROVED
.
策略是预批准的消费规则。当
HEADLESS
支付请求包含
policyId
且请求在策略限制范围内时,会立即自动批准——无需消费者交互。
策略适用场景:
  • 自主AI Agent无需每次交易都经过授权即可完成购买
  • recurring支付(订阅、自动充值)
  • 高频小额交易
直接创建(卖家发起):
http
POST https://api.inflowpay.ai/v1/policies
X-API-Key: <INFLOW_API_KEY>
Content-Type: application/json

{
  "type": "PAYMENT",
  "action": "APPROVE",
  "currency": "USDC",
  "budget": 1000,
  "threshold": 100,
  "period": "MONTHLY",
  "expires": "2026-12-31T00:00:00.000Z"
}
消费者发起请求(消费者需通过设备批准策略):
http
POST https://api.inflowpay.ai/v1/requests/policy
X-API-Key: <INFLOW_API_KEY>
Content-Type: application/json

{
  "userId": "<消费者UUID>",
  "display": "FULL",
  "userDetails": ["EMAIL"]
}
返回
ApprovalResponse
——与登录/支付流程一致。消费者通过SDK或手机完成授权后,策略生效。
策略字段:
  • action
    ——
    APPROVE
    DECLINE
    REVIEW
  • period
    ——
    DAILY
    MONTHLY
    YEARLY
    ONCE
  • budget
    ——周期内总限额
  • threshold
    ——单笔交易最大限额
  • spent
    ——当前已使用额度(
    PolicyResponse
    中为只读字段)
CRUD操作:
http
GET    /v1/policies
GET    /v1/policies/{policyId}
POST   /v1/policies
PUT    /v1/policies/{policyId}
DELETE /v1/policies/{policyId}/delete
将策略附加到支付请求:
json
{
  "userId": "<消费者UUID>",
  "amount": 50,
  "currency": "USDC",
  "display": "HEADLESS",
  "userDetails": ["EMAIL"],
  "policyId": "<策略UUID>"
}
如果请求在预算和限额范围内,响应会直接显示
status: APPROVED

Step 8: Wallet Read Operations

步骤8:钱包查询操作

Read-only access to the seller's wallet state. Useful for dashboards, reconciliation, and balance checks.
http
GET /v1/balances              — all currency balances
GET /v1/balances/{currency}   — single currency balance
GET /v1/transactions          — paginated transaction history
GET /v1/transactions/{id}     — single transaction
GET /v1/users/self            — current authenticated user
All return JSON. See
references/api-reference.md
for full schemas.

只读访问卖家钱包状态。适用于仪表盘、对账和余额检查。
http
GET /v1/balances              — 所有币种余额
GET /v1/balances/{currency}   — 单个币种余额
GET /v1/transactions          — 分页交易历史
GET /v1/transactions/{id}     — 单个交易详情
GET /v1/users/self            — 当前认证用户信息
所有接口返回JSON。完整Schema请查阅
references/api-reference.md

Verification

验证

Run a smoke test to confirm everything works. Adapt the commands to whatever the project uses (npm scripts, curl, http file, etc.):
  1. API key auth — call
    GET /v1/users/self
    with the configured
    X-API-Key
    . Should return the authenticated user. If 401, the key is wrong.
  2. User search — call
    POST /v1/users/search
    with a known email. Confirm 200 +
    userId
    , or 404 if the user doesn't exist.
  3. Payment request — initiate a small test payment to the known
    userId
    . Confirm the response contains a
    requestId
    and
    status: PENDING
    .
  4. Polling (if implemented) — call
    GET /v1/requests/{requestId}
    and confirm the status updates as the consumer interacts.
  5. Webhook (if implemented) — once a real event is delivered, check the handler's logs to confirm: signature verified, payload parsed, handler logic executed, HTTP 200 returned. (You can also use
    GET /v1/events
    to inspect past events and
    POST /v1/events/{eventId}/resend
    to replay one.)
If the user is in production, also recommend:
  • A persistent idempotency store for
    eventId
    s (DB or Redis)
  • Logging all webhook events for audit
  • A dead-letter queue for failed webhook processing

运行冒烟测试确认所有功能正常。根据项目使用的工具(npm脚本、curl、http文件等)调整命令:
  1. API密钥认证——使用配置的
    X-API-Key
    调用
    GET /v1/users/self
    。应返回认证用户信息。如果返回401,说明密钥错误。
  2. 用户搜索——使用已知邮箱调用
    POST /v1/users/search
    。确认返回200 +
    userId
    ,或用户不存在时返回404。
  3. 支付请求——向已知
    userId
    发起小额测试支付。确认响应包含
    requestId
    status: PENDING
  4. 轮询(如果已实现)——调用
    GET /v1/requests/{requestId}
    ,确认状态随消费者操作更新。
  5. Webhook(如果已实现)——收到真实事件后,检查处理器日志,确认:签名验证通过、payload解析成功、处理器逻辑执行、返回HTTP 200。(也可使用
    GET /v1/events
    查看历史事件,使用
    POST /v1/events/{eventId}/resend
    重放事件。)
如果用户处于生产环境,还建议:
  • eventId
    配置持久化幂等性存储(数据库或Redis)
  • 记录所有Webhook事件用于审计
  • 为Webhook处理失败配置死信队列

Agentic User Setup (No Existing Account)

Agent用户设置(无现有账户)

If the user does NOT have an existing InFlow account, create an agentic (programmatic) user. This is a one-time setup and does NOT require existing authentication.
http
POST https://api.inflowpay.ai/v1/users/agentic
Content-Type: application/json

{
  "locale": "EN_US",
  "timezone": "US/Pacific"
}
Returns:
json
{
  "userId": "<uuid>",
  "privateKey": "<your-api-key>",
  "locale": "EN_US",
  "timezone": "US/Pacific",
  "created": "...",
  "updated": "..."
}
The
privateKey
is what goes in the
INFLOW_API_KEY
env var. Store it securely — it cannot be retrieved again.

如果用户没有InFlow账户,可创建Agent(程序化)用户。这是一次性设置,无需现有认证。
http
POST https://api.inflowpay.ai/v1/users/agentic
Content-Type: application/json

{
  "locale": "EN_US",
  "timezone": "US/Pacific"
}
返回:
json
{
  "userId": "<UUID>",
  "privateKey": "<你的API密钥>",
  "locale": "EN_US",
  "timezone": "US/Pacific",
  "created": "...",
  "updated": "..."
}
privateKey
即为
INFLOW_API_KEY
环境变量的值。请安全存储——无法再次获取。

Idempotency

幂等性

Before each step, check if the work is already done. Skip steps that are complete:
  • If
    INFLOW_API_KEY
    is already in
    .env
    /
    .env.example
    , skip env setup
  • If a webhook handler with HMAC verification already exists for InFlow paths, skip the webhook scenario
  • If
    inflow.js
    is already loaded somewhere in the frontend, skip the SDK script tag
  • If there's an existing helper module that wraps
    api.inflowpay.ai
    calls (look for
    INFLOW_API_KEY
    usage or
    inflowpay.ai
    in source), reuse it instead of creating a new one
  • If
    .env
    /
    .env.local
    is not in
    .gitignore
    , add it
Always read the project before writing — don't duplicate existing logic.

执行每个步骤前,检查工作是否已完成。跳过已完成的步骤:
  • 如果
    INFLOW_API_KEY
    已在
    .env
    /
    .env.example
    中,跳过环境变量设置
  • 如果已存在针对InFlow路径的带HMAC验证的Webhook处理器,跳过Webhook场景
  • 如果
    inflow.js
    已在前端某处加载,跳过SDK脚本标签
  • 如果已有封装
    api.inflowpay.ai
    调用的辅助模块(查找
    INFLOW_API_KEY
    使用痕迹或源码中的
    inflowpay.ai
    ),复用该模块而非创建新模块
  • 如果
    .env
    /
    .env.local
    未在
    .gitignore
    中,添加进去
编写代码前务必了解项目——不要重复已有逻辑。

Final Summary (After Implementation)

最终总结(实现后)

After implementing, give the user a clear summary in this format:
  1. Files created or modified — list each one with its purpose
  2. Env vars to set — name + what each is for + where to get the values
  3. What flows are now active — payment / login / register / webhook / polling / policies
  4. Manual steps the user must complete — be explicit about:
    • Getting the API key (from dashboard or via
      POST /v1/users/agentic
      )
    • Registering the webhook URL in the InFlow dashboard (no API for this — flag prominently if a webhook handler was created)
    • Copying the webhook secret into
      INFLOW_WEBHOOK_SECRET
    • Confirming the production SDK URL with InFlow before going live (sandbox URL is in code by default)
  5. Test commands — exact commands to verify each flow
  6. Next steps — what to do after the integration is wired up
Keep the summary terse. The user should be able to complete the integration after reading it.
完成实现后,向用户提供清晰的总结,格式如下:
  1. 创建或修改的文件——列出每个文件及其用途
  2. 需设置的环境变量——名称 + 用途 + 获取途径
  3. 已启用的流程——支付/登录/注册/Webhook/轮询/策略
  4. 用户必须手动完成的步骤——明确说明:
    • 获取API密钥(从控制台或通过
      POST /v1/users/agentic
    • 在InFlow控制台中注册Webhook URL(无API支持此操作——如果创建了Webhook处理器,请突出提示)
    • 将Webhook密钥复制到
      INFLOW_WEBHOOK_SECRET
    • 上线前与InFlow确认生产环境SDK URL(代码中默认使用沙箱URL)
  5. 测试命令——验证每个流程的具体命令
  6. 下一步操作——集成完成后的后续建议
总结需简洁明了。用户阅读后应能完成集成收尾工作。