CanopyAPI docs

Payment intents

Create a payment intent for each user who is about to move funds. Then give that user the deposit address that belongs to them.

Create one per user

Your server creates a payment intent for each user who is about to move funds. Do not share one intent across users. Do not create one before you know who the user is.

Each intent has a deposit address unique to that user. After the deposit settles, the net amount goes to the destination named on the intent.

See payout destinations for what a destination can be and when you can set one.

The create body

POST /api/v1/intents accepts the keys below. Every key is optional. Any other key returns 400 invalid_request.

  • merchantReference (optional, up to 128 characters).
  • metadata (optional).
  • priceUnits (optional). A whole number in base units, sent as a string. Digits only, no leading zero, greater than zero. It sets a minimum for this intent. A deposit at or above the price settles. A smaller deposit waits until the user tops it up. The minimum shown to the user is the higher of this price and the route's own minimum.
  • oneTime (optional boolean). Requires priceUnits. Without it, the create returns a 400. When the total received reaches the price, the intent settles and closes. Later status reads show the final state, and the payout destination is free for a new intent.
  • payoutWallet (optional, 1 to 64 characters).
  • payoutNamespace (optional, the enum "eip155" or "solana").
  • payoutChainReference (optional, 1 to 64 characters).
  • payoutTokenAddress (optional, 1 to 64 characters). A supported token address, or native for the destination chain's native currency. The token is fixed on the intent. If you omit it, Canopy uses your account's matching currency or the chain's default token.
curl -X POST https://www.canopypay.io/api/v1/intents \
  -H "Authorization: Bearer cnpy_sk_live_a1b2c3_<64 hex chars>" \
  -H "Canopy-Version: 2026-09-01" \
  -H "Content-Type: application/json" \
  -d '{
    "merchantReference": "order_10031",
    "payoutWallet": "0x9f2a1c7b3e5d8a604c1f2b9e7d3a5c8b6e4f1d20",
    "payoutNamespace": "eip155",
    "payoutChainReference": "8453"
  }'

The four payout keys set where this intent settles. You cannot change the destination after the intent is created. To set one, two things must both be on:

  • Per-intent destinations on your account. You control this in the dashboard.
  • Dynamic payout mode on the route. Only Canopy can turn this on.

Each one returns its own error when it is off. See payout destinations for both errors, the chains Canopy can deliver to, the other create errors, and how Canopy picks a destination you did not name.

One active intent per payout destination

Only one active intent can exist for each payout destination. A destination here means the combination of your merchant account, payout namespace, chain, wallet and token.

What happens when you create an intent for that destination again:

  • Same terms: you get the existing intent back with created: false and a new widgetToken. The old token stops working.
  • Different priceUnits or oneTime: you get a 409. See idempotency_key_reuse.

EVM addresses match without regard to case. Solana addresses must match exactly. A different chain or token counts as a different destination.

This rule applies as soon as you send any destination field. Canopy fills the missing ones from your account defaults. A create with no destination fields always makes a new intent with a new deposit address.

An intent without oneTime stays active after a deposit. Archive it before you create one with different terms.

What happens after you create one

A successful create returns intentId, inboxAddress and widgetToken. Next, show checkout, let the user fund the address, and wait for settlement. The quickstart walks through each step. Webhooks covers how you learn that a deposit settled.

See also