Skip to main content
You owe money to a list of people. You do not want to build banking, hold their identity documents, or run a payout rail. This recipe wires your platform to Glide so each of your users gets a real Glide account, consents to exactly what you may see, and receives USD you push from a prefunded treasury. Audience: a backend engineer at a creator marketplace, a freelance platform, a payroll product, or anything else that pays its users. Examples use https://glide.co. Supply your own configured credentials, funded treasury and consented recipients before calling the API. Placeholder UUIDs follow the 11111111-1111-4111-8111-111111111111 shape used throughout the Partner API reference.

What you are building

How the money moves

Three balances, in order. Your treasury USD balance. Glide binds your partner record to one business entity and one entity-scoped USD balance. You prefund it by settling real USD into the bound reserve account, and a Glide operator records that settlement against evidence. There is no deposit API you call. The user’s personal Glide USD balance. A batch payout is a synchronous, atomic book transfer inside Glide: a treasury debit and a user credit, both completed, both zero fee, written in one database transaction. When the call returns 200, the money is spendable. Nothing is queued and nothing is pending. The user sees it as incoming, labelled Payout from partner. The user’s bank. The user, not you, drives the cash-out. They convert USD to USDC inside Glide, review the quoted fees, then continue to a bank withdrawal.
Receiving a payout does not require the user to be identity verified. Converting USD on the way to a bank withdrawal does. Payout eligibility also requires an active user, the required consent and a funded, enabled partner treasury. Glide prompts users to verify before conversion and bank payout. Do not build a verification gate in front of your own payouts.

Prerequisites

  • A backend you can put a secret in. Every call in this recipe is server-side.
  • An HTTPS endpoint on port 443 for webhooks, reachable from the public internet.
  • Node 20 or later if you want to run the TypeScript samples, plus curl and jq for the shell ones.
  • Partner credentials from Glide. Step 1 covers how to get them.
1

Get partner credentials from Glide

Ask Glide to create your partner record. You will be asked for:
  • Your partner name and a URL-safe slug. The name is what users see on the consent screen.
  • The exact webhook URLs you want allowlisted. Delivery only goes to an exact match.
  • Whether you intend to push payouts, which decides the treasury:write entitlement.
Glide returns a client_id and a client_secret once. Client IDs look like gpc_<slug>_<8 hex>, secrets like gps_ followed by 32 random bytes in base64url.
Glide stores only a salted scrypt hash of the secret and can never show it to you again. If you lose it, ask Glide to rotate it. Rotation keeps your client_id and immediately kills the old secret, though tokens already minted stay valid for up to an hour.
2

Mint and cache an access token

Glide issues native client_credentials JWTs. There is no authorization server to configure.POST /api/partner/oauth/token with Content-Type: application/x-www-form-urlencoded, a required grant_type=client_credentials, and an optional space-separated scope. Authenticate with HTTP Basic, form-encoding each credential before base64-encoding client_id:client_secret.
Send Authorization: Bearer <access_token> on every other call.Cache it. Tokens last exactly one hour, and issuance is limited to 30 requests a minute per client, counting failed attempts. Minting one per request will rate-limit you under any real load.On a single 401 invalid_token, clear the cache and mint once more. Do not loop. If the second attempt fails too, your credentials or your client status is the problem. Clear the cache on deploy when you rotate the secret.
Send credentials one way only. Combining HTTP Basic with form-field credentials, or repeating any parameter, returns invalid_request. Form bodies are capped at 4096 bytes.
3

Pre-create a user and send the claim link

You bring the email. Glide creates a placeholder account and returns a stable user id plus a link to the consent screen.
Store user_id against your own user row. It is stable forever, including after the person claims the account.created describes the first call for your partner and that email. It does not flip on replay, so a repeated call returns the same id and the same created value. If the person already has a Glide account, you get created: false and their existing id. Pre-creation is limited to 60 requests a minute.Glide sends no email. Put claim_link in your own onboarding email or dashboard, and tell the person which address to sign in with.

What the user sees

They land on the consent screen, sign in, see your partner name and the exact permissions you asked for, and click Allow access. Approving replaces their scope set for you with exactly what they approved, so a narrower re-consent narrows your access. They can revoke any time under Settings then Integrations.
To pay someone you need both payouts:receive and transfers:read on their grant. The payout writes a transfer the user has to be able to see, so receive consent alone is not enough.
The claim link is navigation to a consent screen, not a bearer capability and not a login bypass. It carries no email and no user id, so it is safe to render in your UI. The person must sign in with the email you pre-created. Glide attaches their verified identity only where the email matches and no identity is attached yet, which preserves the Glide user id, your receipt and any grants. Signing in with a different address consents for that other account instead. If someone uses the wrong address, pre-create the one they actually used and send them that link.Partner placeholders have no account type. The owner picks Individual or Company after claiming, through Glide’s normal one-time selection. You cannot set it and you cannot change it later.
4

Read verification, wallets and ledger state

Four reads, each gated by its own consent scope, each limited to 300 requests a minute.
Verification status is one of not_started, in_progress, review, approved or rejected. With no record, it is not_started with null provider and timestamp. You never receive documents, rejection reasons or provider tokens.Wallets come back as an array of evm and solana addresses. An unclaimed user normally has an empty array, so treat empty as “not ready yet” rather than an error.The USD account response is a discriminated union on status. Branch on it before reading account:
Reading this endpoint never starts provisioning. If the user has no receiving account, they have to complete the deposit-details flow inside Glide.Need the user to verify? Request a hosted link with POST /api/partner/kyc/:id/link, no body, limited to 10 requests a minute. It returns status: "created" with a URL, already_approved with a null URL, or pending with a null URL while the provider is still reviewing. Glide handles the callbacks, so you learn the outcome from kyc.status_changed rather than by polling.

Polling the ledger

GET /api/partner/users/:id/transfers returns the user’s personal ledger. Use since as an exclusive lower bound on updated_at, limit between 1 and 100, and follow cursor to the end.
Results sort by updated_at then id, both ascending, at full timestamp precision. Preserve since while paging and only advance your watermark once next_cursor comes back null.
This is an incremental view, not a frozen snapshot. Rows can change while you page. Overlap your watermark by a safe margin and dedupe on (id, updated_at) before applying anything. An invalid cursor returns 400 invalid_cursor: restart from your watermark rather than trying to repair it.
amount_cents is an exact string-encoded minor-unit integer, selected as text so JavaScript cannot round it. Parse it as a big integer or a decimal type, never a float. On older rows, status, currency and completed_at can be null.
5

Receive and verify webhooks

Ask Glide to allowlist your exact URL and configure the endpoint. Glide returns the signing secret once and keeps only encrypted ciphertext. Each allowlisted URL gets its own secret.Four event types arrive, each gated by the matching consent scope:
Each request carries Content-Type: application/json, Idempotency-Key set to the event id, and X-Glide-Signature: t=<unix-seconds>,v1=<hex HMAC-SHA256>.Verify the HMAC over the exact string ${timestamp}.${rawRequestBody} with a constant-time compare:
Four rules make this correct:
  1. Sign over the raw bytes. Capture the body before any JSON middleware touches it.
  2. Enforce a five minute tolerance. The timestamp and signature are regenerated on each retry while the event id and payload stay identical, so tolerance blocks replay without breaking legitimate retries.
  3. Dedupe by event id before side effects. Delivery is at least once, and your receiver can accept an event before Glide records the success.
  4. Return 2xx only after durable acceptance. Network errors, 408, 429 and 5xx are retried. Every other non-2xx, redirects included, is a terminal failure.
Retries run at 30, 60, 120, 240, 480, 960 and 1920 seconds after the previous attempt, for at most eight attempts including any interrupted by a worker restart. The cron cadence can add up to a minute to each delay. Glide requires HTTPS on port 443, resolves your host to public addresses only, pins the connection to the validated IP, and follows no redirects.Before every attempt Glide rechecks your partner status, the endpoint allowlist and the user’s grant. A revoked or replaced grant cancels queued events, and re-granting does not release events queued under the prior consent.
Ordering is not guaranteed, and an event can beat the API response that caused it. When order matters, fetch current state from the read endpoints instead of folding events into a state machine.
6

Pay your users

POST /api/partner/treasury/transfers/batch moves USD from your treasury into 2 to 50 user balances in one atomic transaction.Before the first call, Glide has to have bound your treasury to your business entity and external payer UUID, recorded verified funding, and activated it. That sequence is Go live. Your token needs the treasury:write entitlement, which never appears in user consent.Each recipient needs a pre-creation receipt for your partner, an active account, and both payouts:receive and transfers:read on their grant. There is no verification, wallet or receiving-account gate on this endpoint.

The request

Send Content-Type: application/json and a required Idempotency-Key of 1 to 128 characters from [A-Za-z0-9._:-].
Amounts and the request total are each capped at 9007199254740991. The streamed body limit is 64 KiB, and the rate limit is 10 requests a minute. The same user may appear twice if each entry is a distinct obligation.Generate execution_id and payout_id once, and persist them next to the obligation before you make the call. They are what makes a lost response recoverable.

The response

HTTP 200, including on replay, with one result per item in request order:
Store transfer_id against your payout. It is the correlation key for the ledger read and for transfer.settled. state is always processed, version always 1, fee_cents always "0". total_amount_cents includes reused items, so on a partial replay it can exceed the money this call actually debited.The success audit, batch response, receipts, treasury debit, user credits, activity entry and webhook outbox all commit together. Audit or outbox failure rolls the whole new batch back. You will never receive a successful subset.

Retries, exactly

Never mint a fresh execution_id to retry, and never mark an unconfirmed payout failed. A new id is a new payment. On a timeout, a dropped connection, a 5xx or an unparseable response, resend the identical body with the identical Idempotency-Key. A replayed 200 reconciles you against the transfers that already exist.

What each error means

Every error body carries a request_id that matches the X-Request-Id header and the audit row. Log it.

Caps

Under the treasury lock Glide enforces per-item, per-batch, rolling 24 hour and lifetime amount caps, plus transfer counts per hour and per 24 hours. Only new transfers consume allowance, so replays are free. Hitting a cap exactly is allowed. Cap errors name the violated limit without revealing remaining allowance.

Correlating the webhook

transfer.settled fires from the user’s credit. Match data.transfer_id to the transfer_id you stored from the batch response.
An unmatched event is not necessarily an error. Transfer events cover the user’s whole personal ledger, so movements you did not cause also arrive, and a user may have authorized other platforms. Retain the event, retry the correlation on a backoff, and escalate to a human if it stays unmatched rather than discarding it.
7

Let the user cash out

Once the batch returns 200, the money is in the user’s personal Glide USD balance and is spendable. They see it under Glide balance in the Glide app, and the payout appears as incoming, labelled Payout from partner.From there the user drives everything:
  1. Convert USD to USDC inside Glide, reviewing the quoted fees. This step requires approved verification. Glide explains that on the balance block and links unverified users to identity verification.
  2. Continue to a bank withdrawal from the conversion receipt, or open the bank flow directly against available USDC.
You do not initiate or observe the withdrawal, and you should not gate your payout on it. Track any failed or uncertain withdrawal through the user’s cash-out flow. Do not treat the completed partner payout receipt as proof of bank settlement or a returned balance.If you want to nudge verification, request a hosted link with POST /api/partner/kyc/:id/link and watch for kyc.status_changed with status: "approved".

Go-live checklist

Work through Go live for the full ordered sequence. The short version:
  • Partner created, credentials stored in a secret manager, treasury:write enabled, and a native token verified against a read endpoint.
  • Custody and redemption mandate confirmed, reserve account and settlement evidence identified.
  • Business entity created through its normal owner flow, with its real verification status.
  • Treasury bound with approved caps and mandate expiry, external funding verified and recorded against its receipt.
  • Recipients pre-created, active, and consenting to both payouts:receive and transfers:read.
  • The exact webhook URL allowlisted, its secret configured, and the receiver validated end to end.
  • Proven live in the configured environment: a funded reserve, a small two-recipient batch, the credit visible in each personal USD balance, a signed webhook received and correlated, and a user-authorized bank withdrawal completing.
  • Treasury activated only after all of the above, with monitoring on receipts, audits, rolling usage, webhook delivery and reserve reconciliation.
Two switches when something goes wrong. Treasury suspension stops new payouts while leaving reads, webhooks and authorized replays working. Partner suspension stops all API access and delivery immediately. If a secret leaked, rotate and suspend, because rotation alone leaves already-issued tokens valid for up to an hour.

Reading list

  • Partner API overview gives the model and every endpoint in one table.
  • Authentication covers token claims, scopes, rotation and issuance limits.
  • Users and consent covers pre-creation, claiming and how access is checked.
  • Payouts is the full treasury contract, including caps and admin operations.
  • Webhooks covers signing, retries and consent binding in detail.
  • Errors lists every code with its remediation.