Channel payments
Prefer the capability APIs under /channels/* for multi-provider ramps (Unlimit, PAQ, Vudy OTC, and similar). Classic Vudy payment requests and sends, plus provider-specific follow-ups (hosted Unlimit URLs, PAQ sign/process, OTC proofs), live under /channel/... — see those guides instead of forcing everything through POST /channels/execute.
Prerequisites#
- Dashboard API key
- For quote: resolved profile context (session or
x-profile-id+x-team-id) - For execute: session + completed KYC/KYB as required + KYC gate open
- Wallet that belongs to the acting team and profile when
targetAddressis set
Preferred flow#
- Discover a gate, then load its capabilities
- Complete channel onboarding when discovery requires it
- Validate inputs marked
signedInput - Quote
- Execute
- Poll transaction status and perform returned actions
1. Discover#
Live filtered discovery#
curl -sS "https://api-stg.vudy.app/channels?country=US&service_type=onramp" \
-H "x-api-key: vudy_sandbox_YOUR_KEY"Snapshot discovery#
curl -sS "https://api-stg.vudy.app/channels/discovery?country=US" \
-H "x-api-key: vudy_sandbox_YOUR_KEY"The discovery root returns filters and gate summaries. Select a gateId, then load its providers, capabilities, and parameter definitions:
const root = await fetch(
"https://api-stg.vudy.app/channels/discovery?country=US&service_type=onramp",
{ headers: { "x-api-key": process.env.VUDY_API_KEY } },
).then((r) => r.json());
const gateId = root.data.gates[0].id;
const gate = await fetch(
`https://api-stg.vudy.app/channels/discovery/${gateId}?country=US&service_type=onramp`,
{ headers: { "x-api-key": process.env.VUDY_API_KEY } },
).then((r) => r.json());
const capability = gate.data.gate.capabilities[0];
const capabilityId = capability.capabilityId;If discovery is temporarily unavailable, the API may return 503 — retry shortly or use GET /channels.
Drill into:
GET /channels/discovery/{gateId}GET /channels/discovery/capability/{capabilityId}GET /channels/categories
Use capabilityId and the capability’s params schema in later steps. GET /channels is the alternative execute-oriented discovery endpoint and returns provider-ranked capabilities directly.
2. Complete channel onboarding when required#
If discovery exposes channel onboarding instructions, send the user through that experience and then mark it complete with the session that will execute:
curl -sS https://api-stg.vudy.app/channels/onboarding/complete \
-H "x-api-key: vudy_sandbox_YOUR_KEY" \
-H "Authorization: Bearer SESSION_JWT" \
-H "Content-Type: application/json" \
-d '{"channelId":"CHANNEL_UUID_OR_SLUG"}'Skip this call when discovery does not expose onboarding.
3. Validate signed parameters#
Only validate parameters whose discovery definition includes signedInput: true and validation.url. Call that URL with the raw value and capability id:
curl -sS https://api-stg.vudy.app/channels/paq/validate \
-H "x-api-key: vudy_sandbox_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"paramKey": "codigopaq",
"value": "USER_PAQ_CODE",
"capabilityId": "CAPABILITY_UUID"
}'const { data } = await fetch("https://api-stg.vudy.app/channels/paq/validate", {
method: "POST",
headers: {
"x-api-key": process.env.VUDY_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
paramKey: "codigopaq",
value: "USER_PAQ_CODE",
capabilityId,
}),
}).then((r) => r.json());
// { valid, signature?, response? }
const validationSignature = data.signature;When validation returns valid: true and a signature, send the parameter to execute as the two-item tuple [value, signature]. Revalidate if the value changes.
4. Quote#
Auth: conditional (session or headers). Profile context is required.
curl -sS https://api-stg.vudy.app/channels/quote \
-H "x-api-key: vudy_sandbox_YOUR_KEY" \
-H "x-profile-id: PROFILE_UUID" \
-H "x-team-id: TEAM_UUID" \
-H "Content-Type: application/json" \
-d '{
"capabilityId": "CAPABILITY_UUID",
"amount": 100,
"params": {}
}'await fetch("https://api-stg.vudy.app/channels/quote", {
method: "POST",
headers: {
"x-api-key": process.env.VUDY_API_KEY,
"x-profile-id": profileId,
"x-team-id": teamId,
"Content-Type": "application/json",
},
body: JSON.stringify({
capabilityId,
amount: 100,
targetAddress: "0x…", // optional; else team default when owned
params: {
codigopaq: ["USER_PAQ_CODE", validationSignature],
},
}),
}).then((r) => r.json());Quote returns normalized selection and quote fields. Signed parameters required only for execution may be omitted from quote when the provider can quote without them.
5. Execute#
Auth: session required. Subject to KYC gate (kycOpen).
curl -sS https://api-stg.vudy.app/channels/execute \
-H "x-api-key: vudy_sandbox_YOUR_KEY" \
-H "Authorization: Bearer SESSION_JWT" \
-H "Content-Type: application/json" \
-d '{
"capabilityId": "CAPABILITY_UUID",
"amount": 100,
"targetAddress": "0xTEAM_PROFILE_WALLET",
"params": {
"codigopaq": ["USER_PAQ_CODE", "VALIDATION_SIGNATURE"]
}
}'const { data } = await fetch("https://api-stg.vudy.app/channels/execute", {
method: "POST",
headers: {
"x-api-key": process.env.VUDY_API_KEY,
Authorization: `Bearer ${session}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
capabilityId,
amount: 100,
targetAddress: teamProfileWallet,
params: {
codigopaq: ["USER_PAQ_CODE", validationSignature],
},
}),
}).then((r) => r.json());
// { txId?, channelTableType, recordId, walletFlow, providerPayload? }
// providerPayload examples: Unlimit { url, orderCustomId, ... };
// PAQ internal { txHash }; PAQ external { signPayload, txToExecute, ... }
Returns 201 on success. walletFlow is resolved by the API:
internal: the selected wallet can be signed by the authenticated session; the adapter may execute immediately (checkproviderPayloadfor any immediate result such astxHash).external: the client must useproviderPayloador subsequent statusstepsto finish the flow.
Hosted providers may include a URL in providerPayload. Keep txId, recordId, and the channel slug from discovery.
6. Poll status and perform actions#
curl -sS https://api-stg.vudy.app/channels/CHANNEL_SLUG/TX_ID \
-H "x-api-key: vudy_sandbox_YOUR_KEY"const { data } = await fetch(
`https://api-stg.vudy.app/channels/${channelSlug}/${txId}`,
{ headers: { "x-api-key": process.env.VUDY_API_KEY } },
).then((r) => r.json());
// {
// tx, detail, actionNeeded, steps,
// // steps example (PAQ external):
// // [{ id: "sign", method: "GET", url: "/channel/paq/withdraw/{txId}/sign-payload" },
// // { id: "process", method: "POST", url: "/channel/paq/withdraw/{txId}/process-sponsored-tx",
// // bodyTemplate: { userSignature: "" } }]
// }
Parent transaction statuses: pending → completed | failed | expired | canceled.
When actionNeeded is true, treat each returned step’s method, url, bodyTemplate, and prerequisite as authoritative. Call those /channel/... follow-ups (or capability action URLs) exactly as returned, then poll again. A WAIT step is informational and means the counterparty must act.
Also: GET /v1/tx/{id} and GET /v1/txs?type=profile|team.
Dedicated follow-ups#
| Flow | Guide |
|---|---|
| Unlimit hosted widget | Unlimit hosted ramps |
| PAQ withdraw | PAQ withdrawals |
| Vudy OTC | OTC requester |
| Payment request / send | Payment requests, Sends |