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 targetAddress is set

Preferred flow#

  1. Discover a gate, then load its capabilities
  2. Complete channel onboarding when discovery requires it
  3. Validate inputs marked signedInput
  4. Quote
  5. Execute
  6. 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 (check providerPayload for any immediate result such as txHash).
  • external: the client must use providerPayload or subsequent status steps to 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: pendingcompleted | 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#

FlowGuide
Unlimit hosted widgetUnlimit hosted ramps
PAQ withdrawPAQ withdrawals
Vudy OTCOTC requester
Payment request / sendPayment requests, Sends

Reference#