inflow-payments

v2026.09.24

Integrate InFlow stablecoin payments into any project. Use this skill when the user wants to: accept payments, add a checkout, request a payment, build a payment flow, integrate InFlow, accept USDC/USDT/EURC/PYUSD, set up consumer login via InFlow, register users with InFlow, handle payment webhooks, create spending policies for autonomous agents, build a 0-click headless payment flow, or add InFlow checkout to a marketplace app (note: native multi-recipient splits are not supported by the InFlow API). Also trigger on: 'add inflow', 'inflow payment', 'inflow checkout', 'stablecoin payment', 'crypto payment', 'accept stablecoins', 'pay with usdc', 'inflow webhook', 'inflow login', 'inflow policy'.

GitHub
安装命令
npx skhub add nodeops-app/inflow-payments
Markdown
SKILL.md

InFlow Payments Integration

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.

What InFlow Supports

CapabilityEndpoint(s)Use case
User searchPOST /v1/users/searchFind a consumer by email, mobile, or username
User registrationPOST /v1/requests/registerOnboard a new consumer (out-of-band approval)
LoginPOST /v1/requests/loginAuthenticate a consumer (out-of-band approval)
PaymentPOST /v1/requests/paymentRequest a stablecoin payment from a consumer
PoliciesPOST /v1/policies, POST /v1/requests/policyPre-approved spending rules for headless / 0-click flows
WebhooksInbound POST to your endpointReceive transaction/approval status changes
Polling fallbackGET /v1/requests/{requestId}Check status without webhooks
Wallet readGET /v1/balances, GET /v1/transactionsRead balances and transaction history
Crypto walletsGET/POST /v1/deposit-addresses, /v1/withdrawal-addressesManage on-chain wallet addresses
Agentic usersPOST /v1/users/agenticCreate a programmatic account, get an API key

Currencies: USDC, USDT, EURC, PYUSD (stablecoins only — no fiat) Blockchains: 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.

Defaults & Decision Tree

When the user doesn't specify, use these defaults:

QuestionDefaultWhy
CurrencyUSDCMost 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.


🛑 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.

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.

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.

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.


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.


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_idYour business object — the thing being purchased
inflow_request_idReturned from POST /v1/requests/payment
inflow_transaction_idReturned later in webhook payload (event.data.transactionId) — start NULL, fill on first event
statusYour local state machine: pending / paid / failed
created_atAudit 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.eventIds separately so you don't double-fulfill on retries (InFlow may resend the same event for up to 72 hours).


Happy Path: Add Checkout to a Web App

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.


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.


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

Step 1: Set Up Environment Variables

Add these to the project's existing env file (.env, .env.local, etc. — match the project's convention). Add to .env.example too.

# Required for any InFlow integration
INFLOW_API_KEY=<your-private-key>

# Required if you handle webhooks
INFLOW_WEBHOOK_SECRET=<your-webhook-secret>

# Optional: defaults to 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.

Step 2: Find or Create the Consumer

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):

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):

{ "userId": "8a5c2948-9e08-439e-a7a6-9f0ee335f566" }

On no match (404): initiate registration:

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.

Step 3: Create a Payment Request

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):

{
  "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)

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

For browser-based flows. The frontend loads inflow.js, gets a requestId from the backend, and renders a popup.

Load the SDK:

<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:

// 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.

Step 5: Webhook Handler

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:

{
  "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.

// 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.

Step 6: Polling (Webhook Alternative)

If a webhook listener isn't possible (local dev, batch jobs, simple scripts), poll request status:

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:

GET https://api.inflowpay.ai/v1/transactions/{transactionId}

Where transactionId comes from the approved ApprovalResponse.transactionId.

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

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):

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):

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:

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:

{
  "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.

Step 8: Wallet Read Operations

Read-only access to the seller's wallet state. Useful for dashboards, reconciliation, and balance checks.

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.


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 eventIds (DB or Redis)
  • Logging all webhook events for audit
  • A dead-letter queue for failed webhook processing

Agentic User Setup (No Existing Account)

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.

POST https://api.inflowpay.ai/v1/users/agentic
Content-Type: application/json

{
  "locale": "EN_US",
  "timezone": "US/Pacific"
}

Returns:

{
  "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.


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.


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.

发现
标签

此技能尚未发布标签。

版本
最新版本元数据

版本

v2026.09.24

发布时间

Sep 24, 2026

分类

未分类

许可证

未指定

源路径

skills/inflow-payments

默认分支

main

最新提交

7a0d227

Tree SHA

08dfec1