Loading...
Loading...
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'.
npx skill4agent add nodeops-app/skills inflow-paymentsinflow.jsINFLOW_API_KEY| Capability | Endpoint(s) | Use case |
|---|---|---|
| User search | | Find a consumer by email, mobile, or username |
| User registration | | Onboard a new consumer (out-of-band approval) |
| Login | | Authenticate a consumer (out-of-band approval) |
| Payment | | Request a stablecoin payment from a consumer |
| Policies | | Pre-approved spending rules for headless / 0-click flows |
| Webhooks | Inbound | Receive transaction/approval status changes |
| Polling fallback | | Check status without webhooks |
| Wallet read | | Read balances and transaction history |
| Crypto wallets | | Manage on-chain wallet addresses |
| Agentic users | | Create a programmatic account, get an API key |
USDCUSDTEURCPYUSDAPTOSBASESOLANAWORLDsplits[]PaymentRequestREFUNDEDPOST /v1/webhooks| Question | Default | Why |
|---|---|---|
| Currency | | Most widely-held stablecoin |
| | Required field; email is the minimum useful identifier |
| Display mode | See decision below | Depends on whether a browser is involved |
| Production vs sandbox | Sandbox SDK URL until user confirms | Production SDK URL is not yet documented |
FULLinflow.jsHEADLESSuserIduserIdPOST /v1/users/searchINFLOW_API_KEYX-API-Keyapp/api/.../route.ts+server.ts+page.server.ts+page.svelteserver/api/*.tsserver/routes/*.tsloaderactionprocess.env.INFLOW_API_KEY'use client'undefinedNEXT_PUBLIC_undefinedNEXT_PUBLIC_INFLOW_API_KEYINFLOW_API_KEYNEXT_PUBLIC_app/api/.../route.tsimport.meta.env.INFLOW_API_KEYundefinedVITE_VITE_INFLOW_API_KEYINFLOW_API_KEYVITE_+page.svelte+server.ts+page.server.ts$lib/server/*serverserver/api/*server/utils/*INFLOW_API_KEY<script setup>.vueINFLOW_API_KEYgit push --forceINFLOW_API_KEYINFLOW_WEBHOOK_SECRETPOST /v1/users/searchuserIddisplay: HEADLESSpolicyId{ yourOrderId, requestId, transactionId? }eventIdevent.data.status === "PAID"GET /v1/requests/{requestId}PENDINGinflow.jspolicyIdrequestIdtransactionIdapprovalId| Your column | Source |
|---|---|
| Your business object — the thing being purchased |
| Returned from |
| Returned later in webhook payload ( |
| Your local state machine: |
| Audit trail |
your_order_idinflow_request_idpendinginflow_request_idevent.data.approvalIdrequestIddataApprovalResponseevent.data.transactionIdTransactionResponseevent.eventId┌──────────────────────────────────────────────────────────────────┐
│ 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 │
└──────────────────────────────────────────────────────────────────┘Twofields, two meanings. The webhook envelope has an outerstatus(event.status/DELIVERED/FAILED/PENDING— InFlow's delivery state) and the innerDISABLED(the actual transaction state likeevent.data.status/PAID/PENDING). Always checkINSUFFICIENT_FUNDSfor fulfillment decisions. See Step 5 for details.event.data.status
INFLOW_API_KEYhttps://yourdomain.com/api/webhooks/inflowINFLOW_WEBHOOK_SECRETreferences/references/overview.mdreferences/flows.mdreferences/api-reference.mdpackage.jsonrequirements.txtgo.modCargo.toml.env.env.local.env.example# 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.env.env.local.gitignoreINFLOW_API_KEYuserIduserIdPOST https://api.inflowpay.ai/v1/users/search
X-API-Key: <INFLOW_API_KEY>
Content-Type: application/json
{ "email": "alice@example.com" }{ "userId": "8a5c2948-9e08-439e-a7a6-9f0ee335f566" }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:listsRegisterRequestas optional (onlyuserIdanddisplayare 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 withoutuserDetailsfirst; if the API rejects it, the user must obtain a provisionaluserIdfrom InFlow support or check vendor docs. Treat this as vendor-ambiguous and surface the ambiguity to the user — do not silently work around it.userId
ApprovalResponserequestIdrequestIdemailmobile+15551234567username-_userDetailsBIRTHDATEDEPOSIT_ADDRESSESEMAILMOBILENAMENATIONAL_IDPHYSICAL_ADDRESSUSERNAMEEMAILPOST 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"]
}{
"requestId": "<uuid>",
"type": "PAYMENT",
"status": "PENDING"
}amountcurrencydisplayuserDetailsuserIdpolicyId99.99requestIdFULLrequestIdHEADLESSGET /v1/requests/{requestId}inflow.jsdisplay: FULLinflow.jsrequestId<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.
// 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.
}
}
});requestId🚨 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(or your equivalent URL), and copy the webhook secret into yourhttps://yourdomain.com/api/webhooks/inflowenv var. Until you do this, no events will be delivered."INFLOW_WEBHOOK_SECRET
localhoststatus{
"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 TWOfields:status
(outer) — InFlow's webhook delivery state:event.status/PENDING/DELIVERED/FAILED. This tells you whether InFlow successfully delivered the event. Don't branch on this for business logic.DISABLED (inner) — the actual transaction or approval state:event.data.status,PAID,INITIATED,PENDING,INSUFFICIENT_FUNDS,APPROVED, etc. This is what you check for fulfillment.DECLINED
TRANSACTION_CREATEDTRANSACTION_UPDATEDdatarequestIdtypeApprovalResponsetransactionIdblockchaintransactionHashTransactionResponseevent.type === "TRANSACTION_UPDATED"dataTransactionResponsetransactionIdevent.data.status === "PAID"ApprovalResponsestatusAPPROVEDPENDINGdata.statusPAIDINITIATEDPENDINGINSUFFICIENT_FUNDSGENERAL_ERRORREFUNDEDRETURNEDx-inflow-signature// 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),
);
}x-inflow-signatureeventIdevent.data.approvalIdevent.data.transactionIdevent.type === "TRANSACTION_UPDATED"event.data.transactionIdTransactionResponseApprovalResponseevent.data.status === "PAID"event.data.transactionIdevent.data.statusINSUFFICIENT_FUNDSGENERAL_ERRORRETURNEDINITIATEDPENDINGevent.statusGET https://api.inflowpay.ai/v1/requests/{requestId}
X-API-Key: <INFLOW_API_KEY>ApprovalResponsestatusPENDINGGET https://api.inflowpay.ai/v1/transactions/{transactionId}transactionIdApprovalResponse.transactionIdHEADLESSpolicyIdPOST 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"
}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"]
}ApprovalResponseactionAPPROVEDECLINEREVIEWperiodDAILYMONTHLYYEARLYONCEbudgetthresholdspentPolicyResponseGET /v1/policies
GET /v1/policies/{policyId}
POST /v1/policies
PUT /v1/policies/{policyId}
DELETE /v1/policies/{policyId}/delete{
"userId": "<consumer-uuid>",
"amount": 50,
"currency": "USDC",
"display": "HEADLESS",
"userDetails": ["EMAIL"],
"policyId": "<policy-uuid>"
}status: APPROVEDGET /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 userreferences/api-reference.mdGET /v1/users/selfX-API-KeyPOST /v1/users/searchuserIduserIdrequestIdstatus: PENDINGGET /v1/requests/{requestId}GET /v1/eventsPOST /v1/events/{eventId}/resendeventIdPOST https://api.inflowpay.ai/v1/users/agentic
Content-Type: application/json
{
"locale": "EN_US",
"timezone": "US/Pacific"
}{
"userId": "<uuid>",
"privateKey": "<your-api-key>",
"locale": "EN_US",
"timezone": "US/Pacific",
"created": "...",
"updated": "..."
}privateKeyINFLOW_API_KEYINFLOW_API_KEY.env.env.exampleinflow.jsapi.inflowpay.aiINFLOW_API_KEYinflowpay.ai.env.env.local.gitignorePOST /v1/users/agenticINFLOW_WEBHOOK_SECRET