CanopyAPI docs

Accept your first deposit

From a secret key to a settled payment, in four steps.

  1. Get a secret key

    Every call below authenticates with a secret key, cnpy_sk_live_.... It stays on your server and is never sent to a browser. See credentials for the other two credential types and where each is allowed to go.

  2. Create a payment intent

    Call POST /api/v1/intents from your server, once per end user about to move funds:

    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_10024",
        "metadata": { "userId": "u_7781" }
      }'
    201 Createdjson
    {
      "intentId": "6f1b9c2e4d8a4f1b8c2e4d8a4f1b8c2e",
      "inboxAddress": "0x9f2a1c7b3e5d8a604c1f2b9e7d3a5c8b6e4f1d20",
      "created": true,
      "priceUnits": null,
      "oneTime": false,
      "widgetToken": "2f7a1d9c4b6e8035a1c7d3f6b8e04a2c5d7f9b1e3a6c8d0f2b4e6a8c0d2f4b6e"
    }

    This example carries no destination field, so the deposit settles to your account's own payout wallet.

    To send the net to your user's wallet instead, see payout destinations. That path needs two things on: your account's per-intent-destinations setting, and a route Canopy has already opted into Dynamic mode.

  3. Hand your user a deposit address

    The create response's inboxAddress is a deposit address your user can fund directly, by manual transfer from any supported chain.

    For a checkout page instead, mount the widget with the create response's intentId.

    That id is the whole browser integration: price, destination, fee and supported chains all resolve server-side from it, so there is nothing else to fetch or forward.

    If you bundle, install the package first:

    npm install @canopypay/checkout-sdk@0.7.2

    Then mount it -- or drop in a script tag if you'd rather skip bundling entirely:

    checkout.html
    <script src="https://canopypay.io/v1/canopy.js"></script>
    <script type="module">
      const { intentId } = await (
        await fetch("/my-server/create-canopy-intent")
      ).json();
    
      const handle = Canopy.mount({
        target: "#checkout",
        intentId,
        merchant: "cnpy_pk_live_abc123",
        onEvent: (e) => {
          if (e.type === "paid") showBanner("Payment sent");
        },
      });
    
      payButton.addEventListener("click", () => {
        handle.openNow(window.open("", "canopy_checkout", "width=460,height=640"));
      });
    </script>

    The npm build needs canopyOrigin; the script tag works it out from its own src. See the SDK reference for why.

    Open the popup inside the click handler, synchronously. One await before the call is enough for the browser to block it -- see popup timing.

    Prefer checkout rendered inside your own page rather than a popup? That is surface: "auto", plus one origin to register first -- see embed checkout inline.

  4. Watch for settlement

    Settlement is confirmed by webhook, not by the browser. See webhooks for the event catalog and signature verification.

What's next

  • Payment intents covers the full create-body contract and the one-active-intent-per-wallet rule.
  • Need to name a destination per intent? See payout destinations and the two gates around it.
  • Every operation, with a worked request and response, lives in the API reference.
  • Credentials lists the three types and where each one is allowed to go.