Platform Payouts Guide

Oncade provides two mechanisms for platforms to send money to users through the Platform API:

  • Distributions — Batch payouts to a known list of recipients by email.
  • Campaigns — Reward programs where users link their accounts and earn payouts from defined events.
i

Key difference: Distributions are push-based (sender specifies recipients). Campaigns are pull-based (users join, then earn payouts from triggered events).

Distributions

Distributions let a platform pay a list of recipients in a single batch. Each recipient is identified by email. The system automatically determines the best delivery method per recipient.

Required scopes

distribution:create, distribution:read, lookup:emails,gift:create, distribution:funds:create, distribution:funds:read

All requests require the X-Target-Business-Id header and a grant from the target business.

Listing distributions

Retrieve existing distributions for a business with optional pagination and status filtering.

curl -s \
  -H "Authorization: Bearer $PLATFORM_KEY" \
  -H "X-Target-Business-Id: $BIZ_ID" \
  "https://<host>/api/v1/platform/distributions?page=1&limit=20"

Supports page, limit (max 100), and status query parameters. Requires scope: distribution:read.

Step 1: Preview routing with email lookup

Before creating a distribution you can check which recipients already have an Oncade wallet. This determines whether each person receives a direct transfer or a gift claim email.

Loading diagram…
curl -s -X POST \
  -H "Authorization: Bearer $PLATFORM_KEY" \
  -H "X-Target-Business-Id: $BIZ_ID" \
  -H "Content-Type: application/json" \
  -d '{"emails":["alice@example.com","bob@example.com"]}' \
  https://<host>/api/v1/platform/lookup-emails
  • hasAccount: true — Recipient will receive a direct transfer to their wallet.
  • hasAccount: false — Recipient will receive a gift claim email with a link to collect funds.

Step 2: Create a distribution

There are three ways to specify recipients:

Loading diagram…

Percentage mode

Split a total amount by percentage across recipients.

curl -s -X POST \
  -H "Authorization: Bearer $PLATFORM_KEY" \
  -H "X-Target-Business-Id: $BIZ_ID" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "totalAmountCents": 10000,
    "mode": "percentage",
    "recipients": [
      {"email":"alice@example.com","name":"Alice","percentage":60},
      {"email":"bob@example.com","name":"Bob","percentage":40}
    ],
    "memo": "Q1 revenue split"
  }' \
  https://<host>/api/v1/platform/distributions

Fixed mode

Specify a dollar amount per recipient. The totalAmountCents field is ignored — the total is calculated from recipient amounts.

curl -s -X POST \
  -H "Authorization: Bearer $PLATFORM_KEY" \
  -H "X-Target-Business-Id: $BIZ_ID" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "fixed",
    "totalAmountCents": 0,
    "recipients": [
      {"email":"alice@example.com","name":"Alice","amount":60.00},
      {"email":"bob@example.com","name":"Bob","amount":40.00}
    ]
  }' \
  https://<host>/api/v1/platform/distributions

From a saved SplitTemplate

Reference a previously created split template instead of passing recipients inline.

curl -s -X POST \
  -H "Authorization: Bearer $PLATFORM_KEY" \
  -H "X-Target-Business-Id: $BIZ_ID" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"templateId":"<split_template_id>","totalAmountCents":10000,"memo":"Monthly payout"}' \
  https://<host>/api/v1/platform/distributions

The response includes line items showing the routing decision per recipient:

FieldMeaning
method: "direct_transfer"Funds will go to their Oncade wallet
method: "gift"A gift will be created; recipient receives a claim email

Distribution fees

Every distribution has two categories of fees applied on top of totalAmountCents:

TypeSourceWhen applied
Oncade service feeAuto-applied from the business's pricing configuration (or the global default)Always — cannot be opted out of
Platform feesPassed by your platform in the fees array at creation timeOnly when included in the request

The response always contains totalFeeCents (sum of all fee amounts) and a feeLineItems array detailing each fee. The total amount the business must fund is totalAmountCents + totalFeeCents.

Preview fees before creating

Call the fee-preview endpoint to see which Oncade service fee will be auto-applied before you create a distribution. Requires scope: distribution:read.

curl -s \
  -H "Authorization: Bearer $PLATFORM_KEY" \
  -H "X-Target-Business-Id: $BIZ_ID" \
  "https://<host>/api/v1/platform/distributions/fee-preview"

Returns a fees array. Each entry has label, percentage, flat (in cents), and feeType. If the array is empty, no service fee is configured for that business.

Fee calculation types

The feeType field controls how the flat component scales:

feeTypeFormulaUse case
flat_rate_plus_percentage (default)flat + (percentage × total)Fixed overhead + percentage of the payout pool
per_payout_plus_percentage(flat × recipientCount) + (percentage × total)Per-recipient fee + percentage of the payout pool

Attaching platform fees to a distribution

Pass a fees array in the create request to add fees on top of the Oncade service fee. Each entry requires a recipientWallet — the address that receives this fee payment.

curl -s -X POST \
  -H "Authorization: Bearer $PLATFORM_KEY" \
  -H "X-Target-Business-Id: $BIZ_ID" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "totalAmountCents": 100000,
    "mode": "percentage",
    "recipients": [
      {"email":"alice@example.com","name":"Alice","percentage":60},
      {"email":"bob@example.com","name":"Bob","percentage":40}
    ],
    "fees": [
      {
        "label": "Platform Revenue Share",
        "percentage": 3,
        "flat": 0,
        "feeType": "flat_rate_plus_percentage",
        "recipientWallet": "0xYourPlatformWallet"
      }
    ]
  }' \
  https://<host>/api/v1/platform/distributions
!

Heads up: Fee amounts are calculated at creation time and stored on the distribution. The business owner must fund the full totalAmountCents + totalFeeCents amount. Fees are paid out in the same batch transfer as recipients.

Step 3: Fund and finalize

After creating the distribution, the platform handles the funding operations, then notifies Oncade to send claim emails to gift recipients.

Loading diagram…
curl -s -X POST \
  -H "Authorization: Bearer $PLATFORM_KEY" \
  -H "X-Target-Business-Id: $BIZ_ID" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "distributionId": "<distribution_id>",
    "giftData": [{"giftId":"<gift_id>","claimCode":"<64_hex>"}],
    "transactionHash": "0x..."
  }' \
  https://<host>/api/v1/platform/gifts/mark-funded

This updates gift status to FUNDED and sends claim emails to each gift recipient.

SplitTemplate management

Platforms can pre-create reusable recipient lists to avoid passing recipients on every distribution.

Loading diagram…
!

Limitation: The sender must know all recipient emails ahead of time. There is no mechanism for a recipient to opt in to a distribution. If you need recipients to self-enroll, use Campaigns below.

Campaigns (Join-Style Payouts)

Campaigns solve the problem of paying users you don't know yet. Instead of specifying emails, you define events with payout amounts, and users link their accounts to become eligible.

Required scopes

campaign:create, campaign:read, campaign:update,campaign:delete, campaign:start, campaign:stop,campaign:events:create, campaign:users:link:create,campaign:users:link:read, campaign:users:link:remove,campaign:users:link:staticcampaign:funds:create, campaign:funds:read

Campaign lifecycle

A campaign moves through a defined set of states. Understanding the lifecycle helps you integrate the correct API calls at the right time.

Loading diagram…

Step 1: Create a campaign

Define the campaign with one or more events, each specifying a payout amount in USDC smallest units (6 decimals, so 5000000 = $5.00). Optionally include a webhookUrl to receive campaign-specific webhook events.

Loading diagram…
curl -s -X POST \
  -H "Authorization: Bearer $PLATFORM_KEY" \
  -H "X-Target-Business-Id: $BIZ_ID" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "q1-rewards",
    "publicName": "Q1 Reward Program",
    "events": [
      {"code":"signup_bonus","name":"Signup Bonus","payoutAmount":"5000000"},
      {"code":"referral","name":"Referral Reward","payoutAmount":"10000000"}
    ]
  }' \
  https://<host>/api/v1/platform/campaigns

Managing campaigns

After creation you can retrieve, update, or delete a campaign. Updates allow changing the name, events, payout message, and webhook configuration. Deletion is only allowed for campaigns in the created or stopped state.

EndpointScopeDescription
GET /v1/platform/campaignscampaign:readList all campaigns for the target business
GET /v1/platform/campaigns/{id}campaign:readGet campaign details including status and balance
PATCH /v1/platform/campaigns/{id}campaign:updateUpdate name, events, payout message, or webhook URL
DELETE /v1/platform/campaigns/{id}campaign:deleteDelete a campaign (created or stopped only)
curl -s \
  -H "Authorization: Bearer $PLATFORM_KEY" \
  https://<host>/api/v1/platform/campaigns/$CID

Step 2: Fund the campaign

Create a funding session that the business owner completes through the hosted UI. Sessions expire after 1 hour.

Loading diagram…
curl -s -X POST \
  -H "Authorization: Bearer $PLATFORM_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  https://<host>/api/v1/platform/campaigns/$CID/fund-sessions

The response includes a url the business owner opens to authenticate, choose how much to fund, and complete the transaction.

Fund session lifecycle

Each funding session moves through a simple state machine. Poll the session status to track progress, or cancel a session that is no longer needed.

Loading diagram…
EndpointScopeDescription
POST .../fund-sessionscampaign:funds:createCreate a funding session (idempotent by Idempotency-Key)
GET .../fund-sessions/{handle}campaign:funds:readPoll session status (pending, completed, expired, cancelled)
DELETE .../fund-sessions/{handle}campaign:funds:createCancel a pending session so the URL can no longer be used
curl -s \
  -H "Authorization: Bearer $PLATFORM_KEY" \
  https://<host>/api/v1/platform/campaigns/$CID/fund-sessions/$SESSION_HANDLE
i

Idempotency: Creating a fund session with the same Idempotency-Key returns the existing pending session (200) instead of creating a duplicate. New sessions return 201 with a Location header pointing to the funding URL.

Step 3: Create link sessions (the "join" mechanic)

Instead of specifying emails, you generate link URLs that users visit to opt in. The email field is optional and pre-fills the login form.

Loading diagram…
curl -s -X POST \
  -H "Authorization: Bearer $PLATFORM_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"user@example.com"}' \
  https://<host>/api/v1/platform/campaigns/$CID/link/users/initiate

When a user visits the link URL:

  1. They authenticate (creating an Oncade account if needed)
  2. They approve linking their account to the campaign
  3. They receive a stable userRef that the platform uses to record events

Managing link sessions

After creating a link session, you can check its status or remove a user's link.

EndpointScopeDescription
POST .../link/users/initiatecampaign:users:link:createCreate a link session URL (idempotent by email or Idempotency-Key)
GET .../users/link/details?session={key}campaign:users:link:readGet link session status, userRef, and prefilled email
POST .../users/{userRef}/link/removecampaign:users:link:removeUnlink a user from the campaign by user_ref
curl -s \
  -H "Authorization: Bearer $PLATFORM_KEY" \
  "https://<host>/api/v1/platform/campaigns/$CID/users/link/details?session=$SESSION_KEY"
i

Alternative: Link invites. Link sessions require generating a unique URL per user. If you need a single shareable URL (for email blasts, QR codes, or social media), use link invites instead. Each visit to a link invite URL automatically creates a unique linking session. Manage invites via the campaign:users:link:static scope. See the Link Invite API reference for full details.

Step 4: Start the campaign and record events

Start the campaign with a PUT request, then record events as users perform actions. Each event triggers a payout to the linked user for the configured amount.

Loading diagram…
curl -s -X PUT \
  -H "Authorization: Bearer $PLATFORM_KEY" \
  https://<host>/api/v1/platform/campaigns/$CID/start

The userRef must belong to a user with an approved account link for this campaign. The system validates this before dispatching the event. Requires scope: campaign:events:create. You can also send one-time tips by including payoutAmount with userRef in the same request (no pre-defined event required). See the Platform API reference for the Record Campaign Event endpoint and the campaign tipping feature doc for details.

Step 5: Stop the campaign

When the campaign is finished, stop it with a PUT request. Remaining funds can be withdrawn by the business owner. A stopped campaign can be restarted later if needed.

Loading diagram…
curl -s -X PUT \
  -H "Authorization: Bearer $PLATFORM_KEY" \
  https://<host>/api/v1/platform/campaigns/$CID/stop

Campaign webhooks

Campaign lifecycle events are delivered to your platform's configured webhook URL. If you also set a webhookUrl on the campaign itself, a webhookSecret is included in the payload so you can verify campaign-specific webhooks separately.

EventTrigger
Platform.Campaign.CreatedCampaign created via the API
Platform.Campaign.UpdatedCampaign updated via PATCH
Platform.Campaign.DeletedCampaign deleted
Platform.Campaign.StartedCampaign activated via PUT /start
Platform.Campaign.StoppedCampaign deactivated via PUT /stop
Platform.Campaign.Event.RecordedEvent recorded and payout triggered

Account link webhooks

When users interact with link session URLs, these events are delivered to your webhook:

EventTrigger
User.Account.Link.StartedLink session created via the API
User.Account.Link.SucceededUser authenticated and approved the link
User.Account.Link.FailedLink attempt failed
User.Account.Link.CanceledUser canceled the linking flow
User.Account.Link.RemovedUser unlinked via POST .../link/users/remove

See the Platform API Reference - Webhooks section for full payload examples and signature verification.

When to Use What

NeedDistributionsCampaigns
Known recipients, one-time payoutYes
Recurring payouts to same teamYes (SplitTemplates)
Users self-enroll / joinYes (link sessions)
Event-driven payoutsYes
Recipients unknown ahead of timeYes
Simple percentage/fixed splitsYes

Mimicking distributions with campaigns

If you want a "join to get paid" flow but with a simpler payout model, you can use a campaign with a single event:

  1. Create a campaign with a single event (e.g., "payout") with the desired amount.
  2. Generate link session URLs and share them with intended recipients.
  3. As each user links, record the payout event once per linked user.

This creates a "claim your payout" flow where recipients opt in. The trade-off is that campaigns require upfront funding, whereas distributions are lighter-weight.

API Quick Reference

Distribution Endpoints

All distribution endpoints are under /v1/platform.

MethodPathScopePurpose
GET/distributionsdistribution:readList distributions (paginated)
POST/distributionsdistribution:createCreate distribution
GET/distributions/fee-previewdistribution:readPreview auto-applied Oncade service fee
POST/lookup-emailslookup:emailsPreview recipient routing
POST/gifts/mark-fundedgift:createMark gifts funded & send claim emails
POST/gifts/bulkgift:createCreate gift records in bulk
POST/distributions/{id}/fund-sessionsdistribution:funds:createCreate hosted funding session for a distribution
GET/distributions/{id}/fund-sessions/{handle}distribution:funds:readPoll funding session status
DELETE/distributions/{id}/fund-sessions/{handle}distribution:funds:createCancel a pending funding session

Split Template Endpoints

All split template endpoints are under /v1/platform/split-templates.

MethodPathScopePurpose
GET/split-templatessplitTemplate:readList templates for a business
POST/split-templatessplitTemplate:createCreate template
GET/split-templates/{id}splitTemplate:readGet template details
PATCH/split-templates/{id}splitTemplate:updateUpdate template
DELETE/split-templates/{id}splitTemplate:deleteArchive template

Campaign Endpoints

All campaign endpoints are under /v1/platform/campaigns.

MethodPathScopePurpose
Core
GET/campaignscampaign:readList campaigns
POST/campaignscampaign:createCreate campaign
GET/campaigns/{id}campaign:readGet campaign details
PATCH/campaigns/{id}campaign:updateUpdate campaign
DELETE/campaigns/{id}campaign:deleteDelete campaign
Lifecycle
PUT/campaigns/{id}/startcampaign:startActivate campaign
PUT/campaigns/{id}/stopcampaign:stopDeactivate campaign
POST/campaigns/{id}/eventscampaign:events:createRecord event & trigger payout
Link Sessions
POST/campaigns/{id}/link/users/initiatecampaign:users:link:createCreate user link URL
GET/campaigns/{id}/users/link/detailscampaign:users:link:readGet link session status
POST/campaigns/{id}/users/{userRef}/link/removecampaign:users:link:removeUnlink user from campaign
Link Invites
POST/campaigns/{id}/link-invitescampaign:users:link:staticCreate shareable invite code
GET/campaigns/{id}/link-invitescampaign:users:link:staticList invites
PATCH/campaigns/{id}/link-invites/{code}campaign:users:link:staticUpdate invite
DELETE/campaigns/{id}/link-invites/{code}campaign:users:link:staticRevoke invite
Fund Sessions
POST/campaigns/{id}/fund-sessionscampaign:funds:createCreate funding session
GET/campaigns/{id}/fund-sessions/{handle}campaign:funds:readGet funding session status
DELETE/campaigns/{id}/fund-sessions/{handle}campaign:funds:createCancel pending funding session

References