# Delete a Registered Customer Bank Account
Source: https://docs.spherepay.co/api-reference/bank-account/delete-id
openapi/spherepay.yaml DELETE /v2/bank-account/{id}
Delete a bank account by its ID.
Use this endpoint to permanently remove a registered bank account from SpherePay. Once deleted, the account ID can no longer be used in transfer requests. You cannot delete a bank account that has pending or active transfers associated with it.
This action is irreversible. Ensure you have no active or pending transfers referencing this bank account before deleting it.
# List All Registered Customer Bank Accounts
Source: https://docs.spherepay.co/api-reference/bank-account/get
openapi/spherepay.yaml GET /v2/bank-account
List all bank accounts for a customer with pagination
Use this endpoint to retrieve a paginated list of all bank accounts registered in your SpherePay account. You can optionally filter results by customer ID to fetch only the accounts belonging to a specific customer. Results are returned in descending order of creation time.
# Retrieve a Bank Account by ID
Source: https://docs.spherepay.co/api-reference/bank-account/get-id
openapi/spherepay.yaml GET /v2/bank-account/{id}
Get information about a bank account by its ID
Use this endpoint to retrieve a single bank account by its ID. The response includes masked account details (such as a partially redacted account number), the payment networks registered for the account, and the ID of the owning customer.
# Update Bank Account Payment Rail Settings
Source: https://docs.spherepay.co/api-reference/bank-account/patch-id
openapi/spherepay.yaml PATCH /v2/bank-account/{id}
Update a bank account by its ID. Update either the networks on a non-SWIFT account, or the intermediary BIC on a USD SWIFT account that does not already have one.
Use this endpoint to update a registered bank account. Currently, the only mutable field is `networks`, which controls which payment rails are enabled for the account. The new `networks` value must be compatible with the account's currency — you cannot add an incompatible network after creation.
# Register a Customer Bank Account for Transfers
Source: https://docs.spherepay.co/api-reference/bank-account/post
openapi/spherepay.yaml POST /v2/bank-account
Create a new bank account for a customer. Requires at least one of the customer's verification profiles to be approved.
Use this endpoint to register a customer's existing fiat bank account with SpherePay. SpherePay does not open or manage bank accounts — it links an account your customer already holds to their SpherePay profile so it can be used as the source or destination for fiat transfers. You must register at least one bank account before creating a transfer that involves fiat currency. The customer must have at least one approved verification profile before you can register a bank account on their behalf.
# Remove a Business Representative or UBO
Source: https://docs.spherepay.co/api-reference/business-representative/delete-id
openapi/spherepay.yaml DELETE /v2/business-representative/{id}
Delete a business representative by their ID
Remove a business representative from a business customer's KYB profile. Use this endpoint if a representative was added in error or is no longer associated with the business before the KYB submission is finalized. Deletion is permanent — you will need to register a new representative if the removal was unintentional.
This action is irreversible. Once deleted, the representative's record and any uploaded documents associated with them cannot be recovered. Do not delete a representative after KYB has been submitted or approved.
# List All Business Representatives and UBOs
Source: https://docs.spherepay.co/api-reference/business-representative/get
openapi/spherepay.yaml GET /v2/business-representative
List all business representatives for a customer
Retrieve a paginated list of all business representatives registered in your account. Use the `customerId` query parameter to scope results to the representatives of a specific business customer. The response includes each representative's identity details, ownership stake, roles (`ubo` and/or `director`), and the current state of their verification profile criteria — making this endpoint useful for tracking KYB progress across associated persons.
# Retrieve a Business Representative by ID
Source: https://docs.spherepay.co/api-reference/business-representative/get-id
openapi/spherepay.yaml GET /v2/business-representative/{id}
Retrieve a business representative by their ID
Retrieve the full profile of a single business representative by their ID. The response includes the representative's contact details, ownership stake, assigned roles, and a breakdown of their KYB verification criteria — showing which requirements are complete, which are still required, and any errors that need to be resolved before the representative can be approved.
# Update a Business Representative Before KYB
Source: https://docs.spherepay.co/api-reference/business-representative/patch-id
openapi/spherepay.yaml PATCH /v2/business-representative/{id}
Update an existing business representative
Update mutable fields on an existing business representative. Use this endpoint to correct or supplement information before the representative's KYB verification is submitted — for example, to add a tax identification number, update a residential address, add `countryOfBirth` and `nationality` for an EEA+ business, or adjust representation details such as title, roles, or ownership percentage. Only include fields you want to change; unspecified fields are left unchanged. Validation runs on the merged representative state.
See [EEA+ businesses](/concepts/onboarding/business-kyb#eea-businesses) for EEA+ representative requirements.
# Register a Business UBO for KYB Verification
Source: https://docs.spherepay.co/api-reference/business-representative/post
openapi/spherepay.yaml POST /v2/business-representative
Create a new business representative for a customer.
Register a business representative — an Ultimate Beneficial Owner (UBO), director, control person, or authorized signer — for a business customer. Each individual who directly or indirectly owns 25% or more of the business must be registered with the `ubo` role before the business can be approved for transfers. You can also register directors, control persons, and authorized signers who do not meet the ownership threshold. After registration, complete the representative's KYC by uploading their identity documents and, if using the Sphere-Managed model, generating a face verification link.
Any individual with an ownership stake of 25% or greater must be registered with the `ubo` role. If no single person meets this threshold, register the individual with the highest ownership stake or the person with significant management control. `ownershipPercentage` accepts 0–100; the `ubo` role still requires ≥ 25. A representative without the `ubo` role (for example, director-only) may hold 0–24. EEA+ businesses must also register directors, control persons, and signers — see [EEA+ businesses](/concepts/onboarding/business-kyb#eea-businesses).
# List Customers with Filters and Pagination
Source: https://docs.spherepay.co/api-reference/customer/get
openapi/spherepay.yaml GET /v2/customer
List customers with optional server-side search, filters, and sort.
Use this endpoint to retrieve all customers associated with your SpherePay account. Results are returned in pages, and you can filter by customer status or type to narrow the list. Each item in the response includes the customer's verification profile, so you can quickly identify which customers are ready for transfers.
# Retrieve a Customer with Verification Status
Source: https://docs.spherepay.co/api-reference/customer/get-id
openapi/spherepay.yaml GET /v2/customer/{id}
Get information about a customer by their ID
Use this endpoint to fetch the full details of a single customer by their unique ID. The response includes the customer's current verification profile status, which tells you exactly which KYC/KYB criteria remain outstanding before the customer can initiate a transfer. You can also inspect attached bank accounts and wallets through this record.
# Regenerate a Customer's KYC Link
Source: https://docs.spherepay.co/api-reference/customer/kyc
openapi/spherepay.yaml POST /v2/customer/{id}/kyc-link
Regenerate a hosted KYC link for an existing customer. Only customers created via the onboarding link flow are supported.
Use this endpoint to regenerate a hosted KYC link for an existing customer — for example, when a previously issued link expired before the customer completed verification. The response returns a time-limited URL that you redirect the customer to for any remaining identity checks handled by the verification provider. By default, links expire 4 weeks (28 days) after creation, but this default may change without notice — always rely on the `expiresAt` field in the response rather than assuming a fixed duration.
Hosted verification links collect EEA+ KYC and KYB data automatically when it applies. Integrators using hosted links need no API changes for those requirements.
This endpoint only works for customers created via [Create Customer via Link](/api-reference/customer/kyc-link). Customers onboarded through the direct API flows are not compatible with the onboarding link endpoints — and customers created via onboarding links cannot be onboarded through the direct API flows.
# Create Customer via Link
Source: https://docs.spherepay.co/api-reference/customer/kyc-link
openapi/spherepay.yaml POST /v2/customer/kyc-link
Create a customer via the hosted onboarding link flow. Returns a KYC link and its expiry.
Use this endpoint to create a customer and generate a hosted KYC link in a single request. Instead of creating the customer first and then requesting a link separately, you provide the customer's type, email, phone, and address here, and the response returns the new customer's ID along with a time-limited hosted verification URL. Redirect the customer to this URL to complete their identity checks with the verification provider.
Hosted verification links collect EEA+ KYC and KYB data automatically when it applies. Integrators using hosted links need no API changes for those requirements. See [EEA+ region](/concepts/onboarding/overview#eea-region).
Customers created via this onboarding link flow can only be managed with the onboarding link endpoints — they are not compatible with the direct API onboarding flows. Likewise, customers onboarded through the other API flows cannot be used with the onboarding link endpoints.
The returned URL expires at the time indicated by `expiresAt` — by default, 4 weeks (28 days) after creation. This default may change without notice, so always rely on `expiresAt` rather than assuming a fixed duration. If the link expires before the customer completes verification, generate a fresh one with [Regenerate KYC Link](/api-reference/customer/kyc) using the `customerId` from this response.
# Update Customer Address, Phone, or Email
Source: https://docs.spherepay.co/api-reference/customer/patch-id
openapi/spherepay.yaml PATCH /v2/customer/{id}
Update a customer by their ID. (This endpoint only allows updates of personal information for individual customers)
Use this endpoint to update mutable fields on an existing individual customer. You can update the customer's address, personal information, and metadata at any time before KYC submission. Once a KYC review has been submitted, identity fields such as name, date of birth, and tax identification number are locked and cannot be changed — only contact details and metadata remain editable.
Use this endpoint to backfill EEA+ fields (`accountPurpose`, `countryOfBirth`, `nationality`) on existing individual customers. Validation runs on the merged customer state — send only the fields you are adding or changing. See [EEA+ residents](/concepts/onboarding/individual-kyc#eea-residents).
This endpoint currently supports updates for individual customers only. Business customer fields must be managed through the KYC link flow.
# Create an Individual or Business Customer
Source: https://docs.spherepay.co/api-reference/customer/post
openapi/spherepay.yaml POST /v2/customer
Create a new customer.
Before you can initiate any transfer, you must create a customer record in SpherePay. This endpoint registers either an individual or a business customer and returns a customer object with a unique ID and a `verificationProfiles` array that tracks KYC/KYB completion status. You must specify `type` upfront — it cannot be changed after creation.
Individual customers resident in the EEA+ region and EEA+ incorporated businesses have additional fields that must be present before verification. Those fields are optional at creation. See [EEA+ residents](/concepts/onboarding/individual-kyc#eea-residents) and [EEA+ businesses](/concepts/onboarding/business-kyb#eea-businesses).
# Upload KYC and KYB Identity Documents
Source: https://docs.spherepay.co/api-reference/document/post
openapi/spherepay.yaml POST /v2/document
Upload a document for a customer.
Upload an identity document for a customer or business representative as part of the KYC or KYB verification process. Documents are submitted as multipart form data. Accepted file formats are PDF, JPEG, and PNG, validated by file signature rather than extension. Maximum file size is 10 MB per file, or 8 MB for identity documents (`passport`, `drivers`, `id_card`, `residence_permit`, `br_digital_drivers`, `br_digital_id`). The `target` and `documentType` fields determine how SpherePay routes and processes the upload — individual customers, business customers, and business representatives each have their own set of accepted document types.
Documents for individual customers are uploaded with `target: "customer"`. Documents for business representatives (UBOs) use `target: "business_representative"` and the `targetId` must be the representative's ID, not the customer's.
# Generate a Customer Face Verification Link
Source: https://docs.spherepay.co/api-reference/enhanced-due-diligence/face-verification-link
openapi/spherepay.yaml POST /v2/customer/{id}/face-verification-link
Generate a face verification link for a customer.
Generate a Sumsub face liveness verification link for an individual customer. This endpoint is part of the **Sphere-Managed** KYC onboarding model, where SpherePay hosts the liveness check experience. After calling this endpoint, redirect your customer to the returned `url` so they can complete the face liveness check in their browser. The link expires at the time indicated by `expiresAt` — generate a new one if it has expired before the customer completes verification.
This endpoint is only required in the Sphere-Managed onboarding model. If you are using the Platform-Managed model, upload a liveness report document directly via the [document upload](/api-reference/document/post) endpoint instead.
# Send an OTP Code for Customer Verification
Source: https://docs.spherepay.co/api-reference/enhanced-due-diligence/send-verification-code
openapi/spherepay.yaml POST /v2/customer/{id}/otp-send
Send an OTP code to a customer's contact information.
Send a one-time password (OTP) to a customer's registered email address or phone number as part of contact verification during the KYC flow. After the code is sent, prompt the customer to enter it and then call the [Verify OTP Code](/api-reference/enhanced-due-diligence/verify-verification-code) endpoint to confirm the contact detail. OTP codes expire at the time indicated by `expiresAt` in the response — if the code has expired before the customer submits it, call this endpoint again to send a new one.
# Record Customer Terms of Service Acceptance
Source: https://docs.spherepay.co/api-reference/enhanced-due-diligence/tos
openapi/spherepay.yaml POST /v2/customer/{id}/tos-link
Create a TOS link for a customer.
Generate a hosted Terms of Service acceptance link for a customer. This endpoint is required only in the **Sphere-Managed** onboarding model — in the Platform-Managed model, TOS acceptance is handled upstream by your own platform before the customer is passed to SpherePay. Redirect the customer to the returned `link` so they can review and accept SpherePay's Terms of Service. The link expires at the time indicated by `expiresAt`.
TOS acceptance is a required step in the Sphere-Managed KYC flow. The customer cannot be approved for transfers until they have accepted the Terms of Service. If you are using the Platform-Managed model, skip this endpoint.
# Generate a UBO Face Verification Link for KYB
Source: https://docs.spherepay.co/api-reference/enhanced-due-diligence/ubo-face-verification-link
openapi/spherepay.yaml POST /v2/business-representative/{id}/face-verification-link
Generate a face verification link for a business representative.
Generate a Sumsub face liveness verification link for a business representative (UBO). After calling this endpoint, redirect the representative to the returned `url` so they can complete the face liveness check as part of KYB verification. The link expires at the time indicated by `expiresAt` — if the representative has not completed liveness by that time, call this endpoint again to get a fresh link.
This endpoint targets a business representative, not a customer. Use the representative's ID (prefixed `associatedPerson_`) as the path parameter, not the parent business customer's ID.
# Verify a Customer OTP Code for Email or Phone
Source: https://docs.spherepay.co/api-reference/enhanced-due-diligence/verify-verification-code
openapi/spherepay.yaml POST /v2/customer/{id}/otp-verify
Verify an OTP code for customer's contact information.
Verify an OTP code that was previously sent to a customer's email address or phone number via the [Send Verification Code](/api-reference/enhanced-due-diligence/send-verification-code) endpoint. A successful response confirms that the customer controls the specified contact channel and advances their KYC verification criteria. If the code is expired or incorrect, send a new code and prompt the customer to try again.
# List Events and Their Delivery Status
Source: https://docs.spherepay.co/api-reference/events/get
openapi/spherepay.yaml GET /v2/events
List events for the authenticated application, with optional filtering by webhook endpoint, event type, delivery status, and creation date range.
Use this endpoint to retrieve a paginated list of the events SpherePay has recorded for your application. Every event is queryable whether or not its delivery succeeded, so this is the audit trail behind your webhook traffic. Filter by `type` (an exact name, a resource wildcard such as `transfer.*`, or `*`), by delivery `status`, by `webhookEndpointId`, and by the `createdStart` and `createdEnd` bounds on creation time.
Each item summarizes the event alongside its most recent delivery, including the attempt count and the response your endpoint returned.
Filter by `status=failed` to find everything that needs attention. A `responseCode` of `null` means your endpoint did not respond at all — a timeout or network error rather than an HTTP error.
# Retrieve an Event with Full Delivery History
Source: https://docs.spherepay.co/api-reference/events/get-id
openapi/spherepay.yaml GET /v2/events/{id}
Retrieve an event by its ID.
Use this endpoint to retrieve one event together with every delivery and every attempt made for it. Each attempt records whether it was an `original` or a `replay`, when it ran, whether it succeeded, the response code, the response body (truncated to 200 characters and captured on failures), and the latency.
The `Sphere-Delivery-Id` header on a delivery you received is that delivery's ID, so you can use it to line a request that hit your servers up against the delivery and attempt records here.
To re-send a delivery listed in the history, see [replay a failed event delivery](/api-reference/events/replay).
# Replay a Failed Event Delivery
Source: https://docs.spherepay.co/api-reference/events/replay
openapi/spherepay.yaml POST /v2/events/replay/{eventDeliveryId}
Redeliver an event to its webhook endpoint by the ID of a prior delivery attempt.
Use this endpoint to re-send a delivery to its webhook endpoint. SpherePay makes exactly one automatic attempt per event per endpoint and never retries on its own, so recovering a delivery that failed while your endpoint was down is a deliberate call to this endpoint.
The path takes the **delivery** ID, not the event ID. Find it on the `delivery` object returned by [list events](/api-reference/events/get) or in the full history from [get event](/api-reference/events/get-id), or read it off the `Sphere-Delivery-Id` header of the original request.
A replay re-sends the byte-for-byte identical payload with a fresh `Sphere-Timestamp` and `Sphere-Signature`, plus the headers `Sphere-Delivery-Type: replay` and `Sphere-Replay-Reason: manual`. Your verification code path is the same for originals and replays. A successful replay moves the delivery to `delivered`; a failed one leaves it `failed` and records another attempt. The attempt history is preserved either way.
Replays are delivered at-least-once on top of whatever your endpoint already received, and a delivery that already succeeded can be replayed. Deduplicate on the event `id` and apply the [`sequence` staleness check](/concepts/webhooks/event-payloads#the-sequence-field) so a replay is always safe to receive.
# List All Offloader Wallets for Your Account
Source: https://docs.spherepay.co/api-reference/offloader-wallet/get
openapi/spherepay.yaml GET /v2/offloader-wallet
List all offloader wallets
Retrieve a paginated list of all Offloader Wallets in your account. Use the `customerId` query parameter to narrow results to a single customer. This endpoint is useful for auditing wallet configurations, reconciling on-chain addresses to bank account destinations, or building a customer-facing wallet management interface.
# Retrieve an Offloader Wallet by ID
Source: https://docs.spherepay.co/api-reference/offloader-wallet/get-id
openapi/spherepay.yaml GET /v2/offloader-wallet/{id}
Get an offloader wallet by id
Retrieve the full configuration of a single Offloader Wallet by its ID. The response includes the on-chain address to share with the customer, the linked bank account destination, the blockchain network, and the current fee structure. Use this endpoint to verify a wallet's setup or to look up an address before directing a customer to deposit funds.
# Update an Offloader Wallet Bank Destination
Source: https://docs.spherepay.co/api-reference/offloader-wallet/patch-id
openapi/spherepay.yaml PATCH /v2/offloader-wallet/{id}
Update an offloader wallet by id
Update mutable fields on an existing Offloader Wallet. You can change the destination bank account and reference fields (`achReference`, `wireMessage`, `sepaReference`), adjust the integrator fee rate, or update the return address for failed transactions. The on-chain address and source network are immutable after creation.
`network` is not a mutable field. The destination rail is fixed when you create the Offloader Wallet. To switch rails (for example, from ACH to wire), create a new Offloader Wallet.
# Create an Offloader Wallet for Crypto Payouts
Source: https://docs.spherepay.co/api-reference/offloader-wallet/post
openapi/spherepay.yaml POST /v2/offloader-wallet
Create a new offloader wallet.
Create an Offloader Wallet to give a customer a dedicated on-chain address tied to a specific bank account. Any stablecoin sent to that address is automatically converted to the linked fiat currency and forwarded — no per-transfer API call required. Each customer should have their own wallet; sharing an address across customers makes deposit attribution unreliable.
# List All Rate-Locked Quotes for Your Account
Source: https://docs.spherepay.co/api-reference/quote/get
openapi/spherepay.yaml GET /v2/quote
List quotes for the application. Repeat a currency parameter to match any supplied value; source and destination filters combine.
Retrieve a paginated list of all quotes created by your application. Useful for auditing past rate locks, building a history view in your UI, or finding the most recent active quote for a customer.
# Retrieve a Quote by ID
Source: https://docs.spherepay.co/api-reference/quote/get-id
openapi/spherepay.yaml GET /v2/quote/{id}
Retrieve a single quote by its ID.
Fetch the current state of a single quote. Useful when polling for status changes (`active` → `used` or `active` → `expired`) before triggering downstream logic in your application.
# Create a Rate-Locked Quote
Source: https://docs.spherepay.co/api-reference/quote/post
openapi/spherepay.yaml POST /v2/quote
Create a locked exchange rate quote. The quote is valid for the specified duration and can be used once to create a transfer.
Use this endpoint to lock an exchange rate before creating a transfer. Quotes give the customer a deterministic source-to-destination conversion rate that's valid for a fixed window (30, 60, or 300 seconds). The quote captures the source amount, destination amount, exchange rate, and full fee breakdown (integrator + platform) at the moment of issuance. A quote can be redeemed exactly once by passing its ID when calling [Create Transfer](/api-reference/transfer/post).
Quotes are most useful for **trading** and **cross-border payouts** where the customer needs to see the exact rate they'll receive before committing the transfer. For floating-rate transfers (e.g. payroll, treasury rebalancing), skip the quote and call `/v2/transfer` directly.
# Update Quote Status
Source: https://docs.spherepay.co/api-reference/quote/put-id
openapi/spherepay.yaml PUT /v2/quote/{id}
Mark an active quote as used or expired. Only active quotes can be updated.
Manually transition a quote out of the `active` state. Most quote transitions happen automatically — a quote becomes `used` when redeemed by [Create Transfer](/api-reference/transfer/post), and becomes `expired` when its `expiresAt` timestamp passes. Use this endpoint when you need to explicitly void a quote (e.g. customer abandoned the flow) or mark it consumed by a downstream system that doesn't redeem through `/v2/transfer`.
Only quotes with status `active` can be updated. Attempting to update a quote that's already `used` or `expired` returns a `422` validation error.
# Cancel an Unfunded Pending Transfer
Source: https://docs.spherepay.co/api-reference/transfer/delete-id
openapi/spherepay.yaml DELETE /v2/transfer/{id}
Cancel a specific transfer.
Use this endpoint to cancel a transfer before funds have been received. Cancellation is only possible while the transfer is in `pendingFunding` status — once SpherePay detects an incoming deposit and the transfer moves to `fundsReceived` or beyond, it can no longer be canceled. If funds arrive after cancellation, SpherePay will attempt to return them and the transfer may transition to `returned` or `pendingRefundInformation`.
You cannot cancel a transfer that is in any status other than `pendingFunding`. Verify the transfer status with [GET /v2/transfer/](/api-reference/transfer/get-id) before attempting cancellation.
# List On-Ramp and Off-Ramp Transfers
Source: https://docs.spherepay.co/api-reference/transfer/get
openapi/spherepay.yaml GET /v2/transfer
List all transfers
Use this endpoint to retrieve all transfers associated with your SpherePay account. You can narrow results by customer, transfer status, or transfer type. Results are paginated — use the `page` and `limit` parameters to step through large result sets.
# Retrieve a Transfer with Deposit Instructions
Source: https://docs.spherepay.co/api-reference/transfer/get-id
openapi/spherepay.yaml GET /v2/transfer/{id}
Retrieve a specific transfer
Use this endpoint to fetch the complete details of a single transfer by its unique ID. The response includes the current status, the source and destination resources, fee breakdown, deposit instructions for on-ramp transfers, and network-level tracking identifiers once the transfer has been processed. Poll this endpoint or use webhooks to monitor status transitions throughout the transfer lifecycle.
# Create an On-Ramp or Off-Ramp Transfer
Source: https://docs.spherepay.co/api-reference/transfer/post
openapi/spherepay.yaml POST /v2/transfer
Create a new transfer.
Use this endpoint to initiate a money movement for a verified customer. SpherePay supports two transfer directions: **on-ramp** (fiat → crypto) and **off-ramp** (crypto → fiat). You define the direction by the types you supply for `source` and `destination` — a `bank_account` source with a `wallet` destination creates an on-ramp, and the reverse creates an off-ramp. The response includes deposit instructions your customer needs to fund the transfer.
The customer must have an `approved` verification profile before you can create a transfer on their behalf.
# Deactivate an Onramper Virtual Bank Account
Source: https://docs.spherepay.co/api-reference/virtual-account/deactivate
openapi/spherepay.yaml PATCH /v2/virtual-account/{virtualAccountId}/deactivate
Deactivate a virtual account so it no longer accepts deposits.
Use this endpoint to deactivate an Onramper Account. Once deactivated, the account stops processing new fiat deposits — any funds deposited to the virtual bank account after deactivation will not be converted and forwarded. The account's deposit instructions remain unchanged and the account can be reactivated at any time using the [reactivate endpoint](/api-reference/virtual-account/reactivate).
Deactivating an account stops all new deposit processing immediately. Inform your customer before deactivating their virtual account to avoid unexpected rejections of incoming funds.
# List All Onramper Virtual Bank Accounts
Source: https://docs.spherepay.co/api-reference/virtual-account/get
openapi/spherepay.yaml GET /v2/virtual-account
List all active virtual accounts for the authenticated application.
Use this endpoint to retrieve all Onramper Accounts in your SpherePay account. Each Onramper Account represents a virtual bank account assigned to a customer that automatically converts fiat deposits to stablecoins. The response is an array of account objects, each including deposit instructions, destination wallet details, and current status.
# Retrieve an Onramper Account by ID
Source: https://docs.spherepay.co/api-reference/virtual-account/get-id
openapi/spherepay.yaml GET /v2/virtual-account/{virtualAccountId}
Retrieve a virtual account by its ID.
Use this endpoint to retrieve a single Onramper Account by its ID. The response includes the deposit instructions you need to share with your customer, the destination wallet address where converted stablecoins are delivered, the active/inactive status, and the fee configuration.
# List Transfers for an Onramper Account
Source: https://docs.spherepay.co/api-reference/virtual-account/list-transfers
openapi/spherepay.yaml GET /v2/virtual-account/{virtualAccountId}/transfer
List the transfer history for a specific virtual account. Deprecated: the main `GET /v2/transfer` endpoint already returns virtual account transfers and will support filtering to the same effect.
Use this endpoint to retrieve the list of transfers processed through a specific Onramper Account. Each transfer represents a fiat deposit that was automatically converted to stablecoins and forwarded to the destination wallet. Use this endpoint to reconcile incoming deposits, verify conversion amounts, and attribute payments to individual customers.
Each Onramper Account should belong to a single customer. If multiple customers share a virtual account, matching deposits to customers requires manually correlating by amount and timing — which is error-prone. Always create one Onramper Account per customer.
# Update an Onramper Account Destination Wallet
Source: https://docs.spherepay.co/api-reference/virtual-account/patch
openapi/spherepay.yaml PATCH /v2/virtual-account/{virtualAccountId}
Update the destination, network, or fee configuration of a virtual account.
Use this endpoint to update the destination configuration of an existing Onramper Account. You can change the destination stablecoin, blockchain network, wallet address, and integrator fee. All fields in the request body are optional — include only the fields you want to change. Future deposits will use the updated destination configuration immediately after the update.
# Create a Virtual Bank Account for Fiat On-Ramp
Source: https://docs.spherepay.co/api-reference/virtual-account/post
openapi/spherepay.yaml POST /v2/virtual-account
Create a new virtual account that converts incoming fiat deposits to crypto.
Use this endpoint to create an Onramper Account for a customer. An Onramper Account gives your customer a dedicated virtual bank account number. Any fiat deposited to that account is automatically converted to the specified stablecoin and forwarded to the destination wallet address — no per-transfer API call is required. Always create one Onramper Account per customer, even if multiple customers share the same destination wallet, to ensure deposits can be attributed correctly.
Onramper Accounts are a separate product from the Transfer API. They use different endpoints and do not support bank account creation via the Transfer API.
# Reactivate a Deactivated Onramper Account
Source: https://docs.spherepay.co/api-reference/virtual-account/reactivate
openapi/spherepay.yaml PATCH /v2/virtual-account/{virtualAccountId}/reactivate
Reactivate a previously deactivated virtual account.
Use this endpoint to reactivate a previously deactivated Onramper Account. Once reactivated, the account resumes processing fiat deposits and automatically converting them to stablecoins. The deposit instructions, destination wallet, and fee configuration are preserved from before deactivation — no reconfiguration is required.
# Remove a Registered Customer Crypto Wallet
Source: https://docs.spherepay.co/api-reference/wallet/delete-id
openapi/spherepay.yaml DELETE /v2/wallet/{id}
Delete a wallet by its ID
Use this endpoint to permanently remove a registered wallet from SpherePay. Once deleted, the wallet ID can no longer be used in transfer requests. You cannot delete a wallet that is referenced as the source or destination of any active or pending transfer.
This action is irreversible. Ensure no active or pending transfers reference this wallet before deleting it.
# List All Registered Customer Crypto Wallets
Source: https://docs.spherepay.co/api-reference/wallet/get
openapi/spherepay.yaml GET /v2/wallet
List all wallets with pagination. Optionally filter by customer.
Use this endpoint to retrieve a paginated list of all crypto wallets registered in your SpherePay account. You can optionally filter by customer ID to return only wallets belonging to a specific customer. Results are returned in descending order of creation time.
# Retrieve a Customer Crypto Wallet by ID
Source: https://docs.spherepay.co/api-reference/wallet/get-id
openapi/spherepay.yaml GET /v2/wallet/{id}
Get information about a wallet by its ID
Use this endpoint to retrieve a single registered wallet by its ID. The response includes the wallet address, its blockchain network, and the ID of the owning customer.
# Register a Customer Crypto Wallet for Transfers
Source: https://docs.spherepay.co/api-reference/wallet/post
openapi/spherepay.yaml POST /v2/wallet
Create a new wallet and associate it with a customer.
Use this endpoint to register a customer's existing blockchain wallet address with SpherePay. SpherePay does not create or custody wallets — it links an address your customer already owns to their SpherePay profile so that transfers can be routed to or from that address. You must register at least one wallet before creating a transfer that involves crypto. The wallet address must already exist on the target network before registration.
# Delete a Webhook Endpoint
Source: https://docs.spherepay.co/api-reference/webhook-endpoints/delete-id
openapi/spherepay.yaml DELETE /v2/webhook-endpoints/{id}
Delete a webhook endpoint by its ID. Future events will no longer be delivered to it.
Use this endpoint to stop delivery to a webhook endpoint. Deletion moves the endpoint to the `deleted` state, invalidates its signing secret, and halts every future delivery. Deleted endpoints stop counting against the six-endpoint limit.
Because there is no update operation, deletion is also half of the update path: register the replacement first, confirm it is receiving and verifying deliveries, then delete the endpoint it replaces. Both endpoints receive matching events while they overlap, so handlers must deduplicate on the event `id`.
This action is terminal and cannot be undone. The signing secret is invalidated with it, so a replacement endpoint gets a new secret and your verification configuration has to be updated.
# List All Registered Webhook Endpoints
Source: https://docs.spherepay.co/api-reference/webhook-endpoints/get
openapi/spherepay.yaml GET /v2/webhook-endpoints
List all webhook endpoints for the authenticated application, with optional filtering by status and subscribed event types.
Use this endpoint to retrieve a paginated list of the webhook endpoints registered to your application. Filter by `status` to narrow the list to `enabled`, `disabled`, or `errored` endpoints, and by `subscribedEvents` to find endpoints carrying a given subscription. Signing secrets are never included.
The `subscribedEvents` filter matches **literally** against each endpoint's subscription list rather than expanding wildcards. Filtering by `transfer.*` returns endpoints that subscribed with the `transfer.*` wildcard itself, not endpoints subscribed to `transfer.succeeded`. To find every endpoint that would receive one event, pass both forms: `subscribedEvents=transfer.succeeded,transfer.*,*`.
# Retrieve a Webhook Endpoint by ID
Source: https://docs.spherepay.co/api-reference/webhook-endpoints/get-id
openapi/spherepay.yaml GET /v2/webhook-endpoints/{id}
Retrieve a webhook endpoint by its ID.
Use this endpoint to retrieve a single webhook endpoint by its ID. The response carries the endpoint's URL, its subscribed events, its `apiVersion`, and its current state.
The signing secret is not part of the response. It is returned only when the endpoint is created, so if you no longer hold it, [delete the endpoint and register a replacement](/concepts/webhooks/managing-endpoints#updating-an-endpoint-delete-and-recreate) to obtain a fresh one.
# Register a Webhook Endpoint for Event Delivery
Source: https://docs.spherepay.co/api-reference/webhook-endpoints/post
openapi/spherepay.yaml POST /v2/webhook-endpoints
Register a new webhook endpoint to receive event deliveries for the specified subscribed events.
Use this endpoint to register a URL that SpherePay delivers events to, together with the list of events it should receive. Subscribe with exact event names from the [event catalog](/concepts/webhooks/event-catalog), or with a wildcard — `*` for everything, or a resource wildcard such as `customer.*`. A new endpoint is `enabled` immediately and starts receiving matching events.
The signing secret (prefixed `whsec_`) is returned **exactly once**, in this response. No `GET` returns it and it cannot be retrieved later. Store it securely — you need it to [verify the signature](/concepts/webhooks/verifying-signatures) on every delivery.
Each application may hold at most six active endpoints, counting the `enabled`, `disabled`, and `errored` states. Exceeding that returns `409`. There is no update operation, so changing a URL or a subscription means creating a replacement and deleting the old endpoint — see [managing endpoints](/concepts/webhooks/managing-endpoints#updating-an-endpoint-delete-and-recreate) for the zero-gap sequence.
# SpherePay API Release Notes and Changelog
Source: https://docs.spherepay.co/changelog
Latest SpherePay API updates, including new features, breaking changes, deprecations, compliance updates, and newly supported payment rails and assets.
Learn about all of the most recent updates to the SpherePay API Product. This is where you will find out about new features, API changes, deprecations, compliance updates, and new rails/assets. If you have any questions reach out to [support@spherepay.co](mailto:support@spherepay.co).
## MXN/SPEI transfers now supported via the API
SpherePay now supports MXN (Mexican Peso) transfers via the SPEI instant payment network. Your customer pays in pesos and the beneficiary receives USD, at an exchange rate locked before the transfer is created.
This is SpherePay's first fiat-to-fiat corridor: funds move bank to bank, with no stablecoin leg on either side. MXN transfers are available through the API only and are not yet supported in the dashboard.
**Supported routes:**
* SPEI MXN → Wire USD (domestic)
* SPEI MXN → SWIFT USD (international)
* Transfer limits: \$50–\$1,000,000 USD per transfer
**MXN-specific constraints:**
* Transfers must be created from a quote — call [POST /v2/quote](/api-reference/quote/post), then pass the returned `quoteId` to [POST /v2/transfer](/api-reference/transfer/post).
* Integrator fees are not supported for MXN transfers.
* MXN to USD is the only supported direction.
* SPEI bank accounts require `currency: "mxn"` and `networks: ["spei"]`.
SPEI access requires **verification profile D**, which is enabled together with profile C. An MXN bank account cannot be created until profile D is approved — contact your SpherePay representative to enable it.
For details, see [Supported Rails & Currencies](/concepts/transfers/supported-rails).
## Customer-level verification profiles
Customers can now be enrolled in specific verification profiles with the new `enabledVerificationProfiles` field on `POST /v2/customer` and `PATCH /v2/customer/{id}`. Each profile is a set of verification requirements that unlocks a group of products — for example profile A for USD and EUR transfers, offloader wallets and on-ramper accounts, profile C for USD transfers, including SWIFT, from customers outside the United States, and profile D for MXN transfers over SPEI.
**Why it matters:** as SpherePay adds products and rails, you can enable your existing customers for them without re-onboarding from scratch. Requirements a customer has already satisfied for one profile are carried over to the next, so adding profile C to a customer verified on profile A only asks for what C needs on top.
* **`enabledVerificationProfiles`** — an array of profile letters, for example `["a", "c"]`. Send it at creation to choose the customer's profiles; send the full new array in a `PATCH` to add a profile later. If you omit the field, the customer is enrolled in every profile enabled for your application at creation time. Profile D requires profile C on the same customer. Profile selection applies to API-onboarded customers; hosted KYC links use your application's default profiles.
* **Explicit enrolment for new profiles** — when SpherePay enables a new profile for your application, existing customers are not enrolled automatically. Add the profile per customer when they need it.
* **Per-profile requirements** — each entry in `verificationProfiles` now carries its own `criteria.required`, and each profile is decided independently.
* **Add profiles to existing customers, including approved ones** — `PATCH /v2/customer/{id}` with the new array works whether the customer's other profiles are `incomplete`, `pending` or `approved`. Fields a `pending` or `approved` profile already evaluated stay locked; a `PATCH` that touches one returns the new 400 `customer/field-locked-by-verification-profile`, listing each locked field and the profile locking it. See [When customer data can be updated](/concepts/onboarding/verification-profiles/updating-customer-data).
* **Removing a profile** is allowed only while it is `incomplete`; once `pending` or `approved` the request returns the same error.
* **New error codes** — `customer/verification-profile-family-not-enabled-for-application` when the array names a profile your application is not enabled for, and `customer/verification-profile-not-enabled` when none of the selected profiles applies to the customer's country.
See [Verification profiles](/concepts/onboarding/verification-profile) and [Selecting profiles](/concepts/onboarding/verification-profiles/eligibility).
## Brazilian digital identity documents
SpherePay now accepts two additional `documentType` values on [POST /v2/document](/api-reference/document/post) for Brazilian digital identity documents:
* `br_digital_drivers` — digital driver's license (`CNH digital`)
* `br_digital_id` — digital national ID (`RG digital`)
These types are valid only with `country: "BRA"`. Upload a single image and omit the `side` field. They are supported for individual customers (`target: "customer"`) and business representatives (`target: "business_representative"`). Physical Brazilian documents continue to use `drivers` and `id_card` with front and back.
For the full list of accepted ID types, see [Supported ID and Tax ID Types](/concepts/onboarding/identification-types).
## New: EEA verification fields for individual customers
Individual customers resident in the EEA+ region now require `accountPurpose` (new field), `countryOfBirth`, and `nationality` under `personalInformation` before they can be submitted for verification.
These fields are optional at creation and can be supplied later via `PATCH /v2/customer/{id}`. Until they are provided, they surface as `country_of_birth`, `nationality`, and `account_purpose` requirement codes on the customer's `kyc_profile_a` verification profile. Customers outside EEA+ are unaffected.
Existing EEA+ customers may show these as new outstanding requirements. Backfill them with `PATCH /v2/customer/{id}` — validation runs on the merged customer state.
Hosted verification links collect the new data automatically. Integrators using hosted links need no changes.
For field values, enforcement, and an example request, see [EEA+ residents](/concepts/onboarding/individual-kyc#eea-residents).
## New: EEA business registration number and expanded associated-person requirements
EEA+ incorporated businesses must provide `businessInformation.registrationNumber` plus a new per-country `registrationNumberType` (for example `siren`, `kvk`, `crn`). The registration number is stored and verified separately from the tax ID — do not submit a VAT or tax number as the registration number.
Business representatives support a new `director` role. `ownershipPercentage` now accepts `0`–`100` (previously the minimum was 25); the `ubo` role still requires ≥ 25. EEA+ businesses must have at least one control person and complete KYC for all directors, signers, control persons, and 25%+ owners.
Outstanding items surface as `registration_number`, `eea_control_person_present`, and `eea_ap_kyc_scope` requirement codes on `kyb_profile_a`. Representatives of EEA+ businesses must also provide `countryOfBirth` and `nationality`, which surface on each representative's `ubo_kyc_profile_a` profile.
Existing EEA+ customers and representatives may show these as new outstanding requirements. Backfill them with `PATCH /v2/customer/{id}` and `PATCH /v2/business-representative/{id}` — validation runs on the merged state.
Hosted verification links collect the new data automatically. Integrators using hosted links need no changes.
For registration types, representative roles, and requirement codes, see [EEA+ businesses](/concepts/onboarding/business-kyb#eea-businesses).
## New: `statusHistory` on transfer responses
Transfers returned by the v2 API now carry a `statusHistory` array — the full, timestamped timeline of every status the transfer has moved through, oldest first. It's included on [create](/api-reference/transfer/post), [list](/api-reference/transfer/get), and [retrieve](/api-reference/transfer/get-id) responses.
```json theme={"dark"}
"statusHistory": [
{ "status": "pendingFunding", "transitionedAt": "2026-06-20T14:03:21.000Z" },
{ "status": "processing", "transitionedAt": "2026-06-20T14:05:10.000Z" }
]
```
Until now, building a payment timeline meant recording webhook events on your side. With `statusHistory`, you can give your customers full visibility into where a payment is and how it got there from a single API read — and debugging a stalled transfer no longer means reconstructing its history. The field is additive and fully backwards compatible; no request changes are needed.
## SWIFT international payouts
SpherePay now supports **SWIFT** for off-ramp USD payouts to international bank accounts. Funds are converted from stablecoin to USD and sent as a **cross-border wire over the SWIFT network**, routed to the beneficiary's bank through up to **three intermediary (correspondent) banks** when required.
**Registering a SWIFT beneficiary bank account:**
* Create a USD bank account with `currency: "usd"` and `networks: ["swift"]`
* Provide `accountNumber` (IBAN or BBAN) and the beneficiary bank's `bic` (8 or 11 characters)
* Optionally include `intermediaryBics` — an ordered list of up to 3 correspondent-bank BICs (each 8 or 11 characters, no duplicates)
SWIFT access requires **verification profile C**. A USD SWIFT bank account cannot be created until profile C is approved — contact your SpherePay representative to enable it.
SWIFT support includes both first-party and third-party payments. Read more about the requirements for third-party payments [here](/implementation-guides/third-party-flows).
## Manage offloader wallets from the dashboard
Integrators can now generate and track offloader wallets directly from the dashboard.
* **Generate offloader wallets** — create new offloader wallet addresses without an API call.
* **Wallet transaction history** — view the full transaction history for each offloader wallet, including the transfers associated with it.
## Onboard your customers and view their external accounts
Integrators can now create and onboard their end customers directly from the dashboard, as part of the ongoing Integrators Dashboard rollout.
* **Create & onboard a customer** — add a business or individual end customer and run KYB or KYC verification, with a Terms of Service link per customer and live verification status.
* **Customer external accounts** — view a customer's external accounts directly from their Customer tab.
## Add accounts and create transfers on behalf of your customers
Integrators can now manage on-behalf-of activity directly from the dashboard, as part of the first phased release of the new Integrators Dashboard.
* **Add customer accounts** — add external accounts on behalf of your customers without leaving the dashboard.
* **Create transfers on behalf of customers** — initiate transfers from those accounts on your customers' behalf.
* **Improved transfer details** — a refreshed confirmation and details view makes it easier to review each transfer.
## Redesigned dashboard navigation and Transfer flow
The SpherePay dashboard has been overhauled with clearer navigation and a rebuilt Transfer flow.
**Navigation updates:**
* Onboarding has moved out of the Transfers section into its own "Get Started" sidebar item, giving customers a clearer path through the platform from day one.
* "Bank Accounts" has been renamed to "External Accounts" to better reflect how the product works.
**Transfer flow redesign:** The Transfer flow is now a step-based journey with a dedicated entry page. You can choose between moving your own funds, sending to recipients, or setting up Autoramp accounts.
**Offloader Wallets:** Offloader Wallets now has a dedicated page under Autoramp Accounts, with clear product descriptions and usage guidance so customers have full context before getting started.
## BRL/PIX transfers now supported
SpherePay now supports BRL (Brazilian Real) transfers via the PIX instant payment network.
**Supported routes:**
* Off-ramp: USDC/USDT on Polygon, Ethereum, Base, or Tron → PIX BRL
* On-ramp: PIX BRL → USDC/USDT on Polygon, Ethereum, Base, or Tron
* Transfer limits: R\$1.00–R\$7,500.00 per transfer
**BRL-specific constraints:**
* Only `integratorBpsFeeRate` is supported for fees (`integratorFixedFee` is not allowed).
* Solana is not supported for BRL transfers.
* PIX bank accounts require `currency: "brl"` and `networks: ["pix"]`.
BRL transfers require a separate verification profile (`kyc_profile_b` / `kyb_profile_b`). Contact your SpherePay representative for access.
For details, see [Transfers API — BRL/PIX constraints](/concepts/transfers/overview#brlpix-transfer-constraints) and [Supported Rails & Currencies](/concepts/transfers/supported-rails).
## Named wire payouts
USD wire payouts now display your business name as the sender instead of "Sphere." This is enabled automatically when a customer is approved — no manual configuration needed.
Existing approved customers who have transacted in the last 3 months have been backfilled and will see this change reflected immediately.
## API-first onboarding defaults
SpherePay now defaults to a streamlined onboarding model for API-integrated applications. KYC and UBO liveness checks, phone and email OTP verification, and MSA signing are no longer required during onboarding when these steps are handled upstream by the integrator.
These capabilities remain fully supported and can be enabled as opt-in requirements per application. Existing applications retain their current behavior — only new applications receive the streamlined defaults.
For more information, see the [Customers & Onboarding](/concepts/onboarding/overview) guide.
## Offloader Wallets API and EURC support
### Offloader Wallets API
Offloader Wallets are automated crypto-to-fiat conversion wallets that let your customers receive stablecoins and have them automatically settled to a linked bank account.
**Supported stablecoins and networks:**
* USDC on Arbitrum, Avalanche, Base, Ethereum, Polygon, and Solana
* USDT on Ethereum and Tron
* EURC on Base, Ethereum, and Solana
**Supported fiat destinations:**
* USD via ACH and Wire
* EUR via SEPA
For full details, see the [Offloader Wallets guide](/concepts/automation/offloader-wallets).
### EURC now supported
EURC is now available across SpherePay's on-ramp and off-ramp products on Base, Ethereum, and Solana networks.
## Customer API suite for business customers
Full API support for onboarding and verifying business customers, including UBO registration — giving you complete control over the onboarding UX without redirecting customers to an external flow.
New endpoints available:
* [Register business representative](/api-reference/business-representative/post)
* [Upload UBO identification documents](/api-reference/document/post)
* [Request face liveness verification for UBO](/api-reference/enhanced-due-diligence/ubo-face-verification-link)
* [Submit UBO for verification](/concepts/onboarding/business-kyb#poll-for-verification-result)
For full details, see the [KYB via API guide](/concepts/onboarding/business-kyb).
## Deprecating the Transfer Fee API
**Action required by January 14, 2026.** The `/v1/transfer/fee` endpoint will be removed on January 14, 2026. Migrate before this date to avoid disruption.
Transfer fees are now declared directly in the transfer request body. The standalone fee endpoint is no longer needed and will be retired.
## Customer API suite for individual customers
Full API support for onboarding and verifying individual customers — enabling end-to-end KYC flows inside your own product without redirecting customers to an external page.
New endpoints available:
* [Upload required documents](/api-reference/document/post)
* [Send and verify email OTP](/api-reference/enhanced-due-diligence/send-verification-code)
* [Send and verify phone OTP](/api-reference/enhanced-due-diligence/send-verification-code)
* [Request face liveness verification](/api-reference/enhanced-due-diligence/face-verification-link)
The [Create Customer](/api-reference/customer/post) endpoint now accepts the following fields for API-based KYC:
* `personalInformation.taxIdentificationNumber`
* `personalInformation.taxIdentificationNumberType`
* `personalInformation.taxIdentificationNumberCountry`
* `personalInformation.taxIdentificationNumberDescription`
For full details, see the [KYC via API guide](/concepts/onboarding/individual-kyc) or [KYC via Link](/concepts/onboarding/kyc-via-link) for a faster integration path.
## Onramper Accounts API
Onramper Accounts generate dedicated virtual bank accounts that automatically convert incoming fiat deposits into stablecoins and deliver them on-chain — no manual intervention required.
* Generate dedicated bank accounts per customer or application
* Automatically convert USD/EUR deposits into USDC or USDT
* Deliver converted funds directly to any on-chain wallet address
For full details, see the [Onramper Accounts guide](/concepts/automation/onramper-accounts).
## Starknet network support
Starknet is now supported for USDC `onRamp` and `offRamp` transfers. Use `"network": "starknet"` in the `source` or `destination` of your transfer request.
**Off-ramp via Starknet:**
```json theme={"dark"}
{
"customer": "customer_098xxxxxxxxxxxxxxxxxxxxxxxxxxxfbb",
"amount": "1.12",
"source": {
"id": "wallet_81bxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx9f3",
"network": "starknet",
"currency": "usdc"
},
"destination": {
"id": "bankAccount_cabxxxxxxxxxxxxxxxxxxxxcxxxxx42c",
"network": "ach",
"currency": "usd"
}
}
```
**On-ramp via Starknet:**
```json theme={"dark"}
{
"customer": "customer_098xxxxxxxxxxxxxxxxxxxxxxxxxxxfbb",
"amount": "2",
"source": {
"id": "bankAccount_cabxxxxxxxxxxxxxxxxxxxxcxxxxx42c",
"network": "wire",
"currency": "usd"
},
"destination": {
"id": "wallet_81bxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx9f3",
"network": "starknet",
"currency": "usdc"
}
}
```
## Deprecating `/v1/customer`
**Action required by June 6, 2025.** The `/v1/customer` endpoint will be deprecated on June 6, 2025. Migrate to `/v2/customer` before this date.
`/v2/customer` is the new standard for creating and managing customers and will continue to receive new features. See the [Customer API reference](/api-reference/customer/post) for migration details.
# Offloader Wallets: Automated Crypto-to-Fiat
Source: https://docs.spherepay.co/concepts/automation/offloader-wallets
Offloader Wallets give each customer a dedicated on-chain address that automatically converts incoming stablecoins to fiat bank payouts.
Offloader Wallets automate stablecoin-to-fiat conversion without requiring a per-transfer API call. You create an on-chain wallet address for each customer, and any stablecoin sent to that address is automatically converted to the linked fiat currency and deposited into the customer's registered bank account.
Offloader Wallets are an add-on product. They use different endpoints than the Transfers API and do not support webhooks.
## How it works
When you create an Offloader Wallet, SpherePay provisions a dedicated on-chain wallet address tied to a specific bank account. The full flow is:
1. You register a bank account for the customer.
2. You create an Offloader Wallet linking the customer's on-chain address to their bank account.
3. You share the wallet address with your customer.
4. The customer sends stablecoins to the offloader address.
5. SpherePay automatically converts the stablecoins to fiat and initiates a payout to the linked bank account — no additional API calls required.
## When to use Offloader Wallets vs. the Transfers API
Use Offloader Wallets when you want fully automated, deposit-triggered conversion with no per-transfer API call and no webhook dependency. Use the Transfers API when you need explicit per-transfer control or event-driven webhook notifications.
## One wallet per customer
An Offloader Wallet has no per-transfer attribution and does not support webhooks. If multiple customers share a single offloader address, identifying who sent a deposit requires manually matching by amount, timing, or source wallet — which is error-prone. Always create one Offloader Wallet per customer.
## Supported stablecoins and networks
| Stablecoin | Supported Networks |
| - | - |
| USDC | Arbitrum, Avalanche, Base, Ethereum, Polygon, Solana |
| USDT | Ethereum, Tron |
| EURC | Base, Ethereum, Solana |
## Supported fiat destinations
| Currency | Rails |
| - | - |
| USD | ACH, Wire |
| EUR | SEPA |
The destination `network` is fixed at wallet creation and cannot be changed. To use a different rail on the same bank account, create a new Offloader Wallet. If the beneficiary requires a named payment (sender's business name visible on the statement), create the wallet with `network: "wire"` — every payout the wallet triggers uses its configured rail. See [Supported Rails](/concepts/transfers/supported-rails).
## Destination configuration
Set the fiat destination when you create the wallet. The `destination` object includes the bank account, currency, and `network` (`ach`, `wire`, or `sepa`).
Use `wireMessage` for instructions to the receiving bank — for example, for-further-credit (FFC) lines. `wireMessage` applies to wire payouts only. For ACH, use `achReference` instead. For SEPA, use `sepaReference` (6–140 characters).
Set only the field that matches the wallet's `network`. The API rejects the request if you send a field for another rail.
## Integration steps
Before you begin, confirm that Offloader Wallets are enabled for your SpherePay application. Contact your sales representative if you need access.
Each customer must be registered via the [Create Customer](/api-reference/customer/post) API, have accepted the Terms of Service, completed KYC (for individuals) or KYB (for businesses), and have a customer status of `approved`.
Create a bank account record for the customer using the bank account API. This is the fiat destination account where converted funds will be paid out.
Call `POST /v2/offloader-wallet` with the customer ID, the stablecoin currency and network to accept, and a destination object pointing to the customer's bank account.
```json theme={"dark"}
{
"customerId": "customer_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
"currency": "usdc",
"network": "ethereum",
"destination": {
"bankAccountId": "bankAccount_6221e8d4299f4a889bd882255e92f581",
"currency": "usd",
"network": "ach",
"achReference": "REF123"
}
}
```
The response includes the on-chain `address` your customer will send stablecoins to.
```json theme={"dark"}
{
"id": "wallet_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
"customerId": "customer_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
"address": "0x4838B106FCe9647Bdf1E7877BF73cE8B0BAD5f97",
"currency": "usdc",
"network": "ethereum",
"destination": {
"bankAccountId": "bankAccount_6221e8d4299f4a889bd882255e92f581",
"currency": "usd",
"network": "ach",
"achReference": "REF123"
},
"fees": {
"integratorFee": {
"bpsRate": "100",
"currency": "usdc"
},
"platformFee": {
"bpsRate": "30",
"currency": "usdc"
}
},
"returnAddress": "0x4838B106FCe9647Bdf1E7877BF73cE8B0BAD5f97",
"created": "2024-06-15T10:30:00.000Z",
"updated": "2024-06-15T12:00:00.000Z"
}
```
The `address` field in the response is the on-chain wallet address. You can also retrieve it at any time with `GET /v2/offloader-wallet/{offloader_wallet_id}`.
Provide the customer with the `address` from the response. Instruct them to send only the configured stablecoin and network to this address — for example, USDC on Ethereum only.
Once your customer sends stablecoins to the offloader address, SpherePay automatically converts the funds and initiates a fiat payout to the linked bank account. No further API calls are required on your end.
## Additional API operations
In addition to creating and retrieving offloader wallets, the API supports:
* `PATCH /v2/offloader-wallet/{id}` — update the destination bank account
* `GET /v2/offloader-wallet` — list all offloader wallets
Transfer history is available through the standard Transfer API, not a wallet-specific endpoint. Use [Get a Transfer](/api-reference/transfer/get-id) or [List Transfers](/api-reference/transfer/get) filtered by offloader wallet.
## Notes
Webhook notifications are not currently supported for Offloader Wallets. Poll the Transfer API to check the status of individual conversions.
For information on applicable fees and transaction limits, contact your SpherePay sales representative.
## Supported regions
Offloader Wallets are available to end-users globally, except in regions sanctioned or otherwise prohibited by law. Contact SpherePay for a current list of restricted regions.
# Onramper Accounts: Automated Fiat-to-Stablecoin
Source: https://docs.spherepay.co/concepts/automation/onramper-accounts
Onramper Accounts give each customer a dedicated virtual bank account that automatically converts fiat deposits to stablecoins on-chain.
Onramper Accounts let you automate fiat-to-stablecoin conversion without issuing a per-transfer API call for each deposit. You create a virtual bank account for each customer, and any fiat deposited to that account is automatically converted to the linked stablecoin and delivered to the customer's on-chain wallet — no polling, no manual conversion step.
Onramper Accounts are an add-on product. They use different endpoints than the Transfers API and do not support bank account creation via the Transfers API.
## How it works
When you create an Onramper Account, SpherePay provisions a dedicated virtual bank account tied to a specific on-chain wallet address. The full flow is:
1. You create an Onramper Account for a customer, linking their virtual bank account to a destination wallet.
2. You share the virtual bank account details (account number, routing number, beneficiary name) with your customer.
3. The customer makes a fiat deposit to the virtual account via ACH, Wire, or SEPA.
4. SpherePay automatically converts the deposit to the configured stablecoin.
5. The converted stablecoin is delivered to the customer's on-chain wallet — no additional API calls required.
## When to use Onramper Accounts vs. the Transfers API
Use Onramper Accounts when you want fully automated, deposit-triggered conversion with no per-transfer API call. Use the Transfers API when you need explicit per-transfer control, webhooks, or a custom flow triggered by your backend.
## One account per customer
An Onramper Account has no per-transfer attribution. If multiple customers share a single virtual account, identifying who sent a deposit requires manually matching by amount, timing, or source bank — which is error-prone. Always create one Onramper Account per customer, even if they share the same destination wallet.
## Supported rails and currencies
| Direction | Currency | Rails |
| - | - | - |
| Fiat in | USD | ACH, Wire |
| Fiat in | EUR | SEPA |
| Stablecoin out | USDC | Arbitrum, Avalanche, Base, Ethereum, Polygon, Solana |
| Stablecoin out | USDT | Ethereum, Tron |
## Integration steps
Before you begin, confirm that Onramper Accounts are enabled for your SpherePay application. Contact your sales representative if you need access.
Each customer must be registered via the [Create Customer](/api-reference/customer/post) API, have accepted the Terms of Service, completed KYC (for individuals) or KYB (for businesses), and have a customer status of `approved`.
Call `POST /v2/virtual-account` with the customer ID, source currency, destination stablecoin, network, and wallet address.
```json theme={"dark"}
{
"customerId": "customer_1ab2c3d4",
"sourceCurrency": "USD",
"destinationCurrency": "USDC",
"network": "ethereum",
"walletAddress": "0x1234...abcd"
}
```
The response includes the virtual bank account deposit instructions and the destination configuration.
```json theme={"dark"}
{
"id": "virtualAccount_987xyz654",
"active": true,
"fees": {
"totalBpsRate": "20",
"integratorFee": {
"bpsRate": "10"
},
"platformFee": {
"bpsRate": "10"
}
},
"depositInstructions": {
"currency": "USD",
"bankName": "Bank of Example",
"bankAccountNumber": "000123456789",
"bankRoutingNumber": "1234567890",
"bankBeneficiaryName": "John Doe",
"bankBeneficiaryAddress": "123 Main St, Anytown, USA"
},
"destination": {
"currency": "USDC",
"walletAddress": "0x1234...abcd",
"network": "ethereum"
},
"created": "2025-04-20T21:49:46.697Z",
"updated": "2025-04-20T21:50:18.854Z"
}
```
Use `GET /v2/virtual-account/{virtual_account_id}` to retrieve the virtual account and confirm the deposit instructions are correct before sharing them with your customer.
Provide the customer with the `depositInstructions` from the response: account number, routing number, beneficiary name, and beneficiary address. Instruct them to use these details when making a fiat transfer from their bank.
Once your customer deposits fiat to the virtual account, SpherePay automatically converts the funds and delivers the stablecoin to the configured wallet address. No further API calls are required on your end.
Conversion is instant once funds arrive at the virtual account. Transfer time from the customer's bank to the virtual account depends on the rail (ACH, Wire, or SEPA).
## Additional API operations
In addition to creating and retrieving virtual accounts, the API supports:
* `PATCH /v2/virtual-account/{id}` — update the destination wallet or currency
* `GET /v2/virtual-account` — list all virtual accounts
* `PATCH /v2/virtual-account/{id}/deactivate` — deactivate a virtual account
* `PATCH /v2/virtual-account/{id}/reactivate` — reactivate a deactivated account
To check the status of individual transfers, use the [List Virtual Account Transfers](/api-reference/virtual-account/list-transfers) endpoint.
## Account verification
Some external platforms, such as banks, payroll providers, or payment apps, may require customers to verify ownership of their virtual account before linking it. These platforms can initiate microdeposit or challenge deposit verification by sending one or more small deposits to the Onramper Account.
SpherePay does not complete verification on behalf of the customer. When a verification deposit arrives, SpherePay records it as a transfer with `type: microdeposit` and surfaces the amount through the Transfers API. The customer completes verification in the external platform that initiated the flow by confirming the exact deposit amount(s).
Common patterns:
* **Micro-deposit verification** — one or more small deposits, often under \$1, are sent to the virtual account.
* **Challenge deposit verification** — one or more small deposits are made, and the customer confirms the exact amount(s) in the external platform.
Use `GET /v2/transfer?type=microdeposit` to list verification deposits.
## Supported regions
Onramper Accounts are available to end-users globally, except in regions sanctioned or otherwise prohibited by law. Contact SpherePay for a current list of restricted regions.
# KYB for Business Customers
Source: https://docs.spherepay.co/concepts/onboarding/business-kyb
Onboard business customers through KYB verification. Submit company documents, register UBOs, and complete identity verification via API or hosted link.
KYB (Know Your Business) is a legal identity and compliance verification process required before a business customer can send or receive funds through SpherePay. It is mandated by anti-money laundering (AML) and regulatory requirements and must be completed once per business. KYB includes not only verifying the business entity itself, but also verifying the individuals who ultimately own or control it.
## Required information
SpherePay requires the following information to verify a business customer. Fields marked **API** are submitted via request body; fields marked **Document** are uploaded as files.
| Field | Source | Notes |
| - | - | - |
| Legal name, trade name & business description | API | `businessInformation.legalName`, `tradeName`, and `description` |
| Entity type & NAICS industry code | API | `businessInformation.entityType` and `naicsCode` |
| Website & incorporation date | API | `businessInformation.website` and `incorporatedOn` |
| Physical & mailing address | API | `addresses` array — both `registered` and `operating` types required |
| Business identification number | API | `businessInformation.identificationNumber` — accepted types vary by country |
| Company registration number | API | `businessInformation.registrationNumber` and `registrationNumberType` — required for EEA+ businesses. Distinct from the tax ID. See [EEA+ businesses](#eea-businesses) |
| Estimated annual revenue & expected monthly payments | API | `businessInformation.estimatedAnnualRevenueInUsd` and `expectedMonthlyPaymentsInUsd` |
| Account purpose & source of funds | API | `businessInformation.accountPurpose` and `primarySourceOfFunds` |
| Regulated activities & compliance details | API | `businessInformation.regulatedActivities` and related compliance fields |
| Incorporation certificate | Document | `POST /v2/document` with `documentType: "incorporation_certificate"` |
| Shareholder registry | Document | `POST /v2/document` with `documentType: "shareholder_registry"` |
| Proof of business address | Document | `POST /v2/document` with `documentType: "proof_of_address"` |
| UBO name, residential address & date of birth | API | Provided in the business representative registration request |
| UBO tax identification number | API | `personalInformation.taxIdentificationNumber` in the UBO registration request |
| UBO ownership percentage & role | API | `representationDetails.ownershipPercentage` and `roles` |
| UBO government-issued identity document | Document | `POST /v2/document` with `target: "business-representative"` |
EEA+ businesses must also provide a company registration number and complete KYC for directors, signers, and control persons, not only 25%+ owners. See [EEA+ businesses](#eea-businesses).
In certain circumstances SpherePay may request additional documentation — for example, for businesses in regulated or high-risk industries, those with complex ownership structures, or those operating in higher-risk jurisdictions. SpherePay will contact you directly when this applies.
## KYB flow overview
The flow below shows the required steps for business KYB. The only step that differs between onboarding models is TOS and MSA acceptance.
1. Create business customer with full `businessInformation` and `addresses`
2. Generate TOS/MSA link → redirect business representative to accept
3. Upload business documents (incorporation certificate, shareholder registry, proof of address)
4. Register associated persons (UBOs with ≥ 25% ownership; EEA+ businesses also require directors, control persons, and signers)
5. Upload identity documents for each UBO
6. Complete face liveness verification for each UBO (Sumsub SDK, in-flow)
7. Poll `GET /v2/customer/{id}` until `status` reaches `approved`
1. Create business customer with full `businessInformation` and `addresses`
2. Upload business documents
3. Register each UBO
4. Upload identity documents for each UBO
5. Upload liveness report document for each UBO
6. Poll `GET /v2/customer/{id}` until `status` reaches `approved`
## Integration methods
Full control over each step. Use this for a custom onboarding UX embedded in your product.
Fastest integration. SpherePay hosts the entire verification experience.
***
## KYB via API
Use this guide to onboard a business customer step by step via the SpherePay API. The example below uses the Sphere-Managed model (default).
**Before you begin**, ensure you have:
* A SpherePay API key
* The business's legal details (name, entity type, address, identification number)
* Business documents ready for upload (incorporation certificate, shareholder registry, proof of address)
* UBO information and identity documents for each qualifying individual
If your application is enabled for more than one verification profile, choose the customer's profiles at creation with `enabledVerificationProfiles`. See [Onboard a customer with verification profiles](/concepts/onboarding/verification-profiles/onboard-new-customer).
Call `POST /v2/customer` with `type: "business"`. Include the full `businessInformation` object and both address types in `addresses`.
```json theme={"dark"}
{
"type": "business",
"email": "contact@acmecorp.example.com",
"phone": "+14155551234",
"addresses": [
{
"type": "registered",
"country": "USA",
"line1": "123 Main Street",
"line2": "Suite 100",
"city": "Chicago",
"state": "IL",
"postalCode": "60601"
},
{
"type": "operating",
"country": "USA",
"line1": "456 Market Street",
"line2": "Floor 10",
"city": "Chicago",
"state": "IL",
"postalCode": "60602"
}
],
"businessInformation": {
"legalName": "Acme Corporation Inc.",
"tradeName": "Acme Corp",
"entityType": "corporation",
"description": "Enterprise software solutions for supply chain management.",
"naicsCode": "511210",
"website": "https://acmecorp.example.com",
"incorporatedOn": "2020-03-15",
"identificationNumberType": "ein",
"identificationNumber": "12-3456789",
"identificationNumberCountry": "USA",
"estimatedAnnualRevenueInUsd": "1000000_9999999",
"expectedMonthlyPaymentsInUsd": "50000",
"accountPurpose": "investment_purposes",
"primarySourceOfFunds": "sales_of_goods_and_services",
"primarySourceOfFundsDescription": "Revenue from software licensing and consulting",
"isDao": false,
"regulatedActivities": ["none_of_the_above"],
"participatesInRegulatedFinancialActivity": false,
"operatesInProhibitedCountries": false
}
}
```
The `businessInformation.identificationNumberType` accepted values vary by country — for example, `ein` for the United States, `uen` for Singapore, or `crn` for the United Kingdom.
EEA+ businesses must also provide `registrationNumber` and `registrationNumberType`. These are separate from the tax identification number. See [EEA+ businesses](#eea-businesses).
Generate a TOS link and redirect the business representative to accept the Terms and Conditions and the Master Service Agreement.
```bash theme={"dark"}
POST https://api.spherepay.co/v2/customer/{id}/tos-link
```
This step can be done in parallel with uploading business documents.
This step applies to the Sphere-Managed model only. In Platform-Managed, TOS and MSA acceptance must be embedded in your platform's own terms prior to onboarding.
Upload the required business documents. Use `target="customer"` for all business entity documents.
```bash theme={"dark"}
curl --location 'https://api.spherepay.co/v2/document' \
--header 'Authorization: Bearer ' \
--form 'target="customer"' \
--form 'targetId="customer_2f283221a9d44ada800ac7f11f640402"' \
--form 'documentType="incorporation_certificate"' \
--form 'file=@"/path/to/your/document.pdf"'
```
Repeat for `shareholder_registry` and `proof_of_address` document types.
A **UBO (Ultimate Beneficial Owner)** is any individual who owns **25% or more** of the company. Register each qualifying individual via `POST /v2/business-representative`. Repeat this step for every UBO.
You can also register directors, control persons, and authorized signers. `representationDetails.roles` accepts `ubo` and `director` (up to two distinct roles). `ownershipPercentage` accepts any integer from 0 to 100; if the `ubo` role is present, ownership must still be ≥ 25.
```json theme={"dark"}
{
"customerId": "customer_2f283221a9d44ada800ac7f11f640402",
"type": "individual",
"email": "james.wilson@acmecorp.example.com",
"phone": "+13125559876",
"address": {
"line1": "742 North Wabash Avenue",
"line2": "Apt 4B",
"city": "Chicago",
"state": "IL",
"postalCode": "60611",
"country": "USA"
},
"personalInformation": {
"taxIdentificationNumber": "987654321",
"taxIdentificationNumberType": "ssn",
"taxIdentificationNumberCountry": "USA"
},
"representationDetails": {
"roles": ["ubo"],
"ownershipPercentage": "50",
"isControlPerson": true,
"isSigner": true,
"title": "CEO",
"relationshipEstablishedAt": "2020-03-15"
}
}
```
All individuals who meet the 25% ownership threshold must be registered and verified. If more than one individual qualifies, repeat this step for each one. EEA+ businesses have additional associated-person requirements — see [EEA+ businesses](#eea-businesses).
Upload identity documents for each UBO. Use `target="business-representative"` — these documents belong to the individual, not the business entity.
```bash theme={"dark"}
curl --location 'https://api.spherepay.co/v2/document' \
--header 'Authorization: Bearer ' \
--form 'target="business-representative"' \
--form 'targetId="associatedPerson_4914a2f6226e42cc8d207ead9573b29f"' \
--form 'documentType="id_card"' \
--form 'side="front"' \
--form 'file=@"/path/to/your/document.jpg"' \
--form 'country="SGP"'
```
Each UBO requires liveness verification. Check the UBO's `required` array via `GET /v2/business-representative/{id}`, then perform **exactly one** of the following:
* **`liveness_check` in required** — Generate a face verification link for the UBO and redirect them to complete an interactive liveness check via the Sumsub SDK.
* **`liveness_report_document` in required** — Upload a liveness report document for the UBO from your identity verification provider.
Repeat for each registered UBO.
Once all required steps are complete for both the business and its UBOs, SpherePay processes the verification automatically — no submit call is needed. Poll `GET /v2/customer/{id}` until `status` reaches `approved`.
```json theme={"dark"}
{
"id": "customer_2f283221a9d44ada800ac7f11f640402",
"email": "contact@acmecorp.example.com",
"verificationProfiles": [
{
"name": "kyb_profile_a",
"status": "approved",
"criteria": {
"complete": [
"email_address",
"phone_number",
"operating_address",
"registered_address",
"legal_name",
"identification_number",
"business_representatives",
"incorporation_cert_document",
"shareholder_registry_document",
"proof_of_address_document"
],
"pending": [],
"required": [],
"errors": []
}
}
],
"type": "business"
}
```
When `required` is empty and `status` is `approved`, the business customer is fully onboarded and ready to transfer.
KYB review typically takes 2–7 business days after all required documents and data are submitted.
***
## EEA+ businesses
Sphere enforces additional KYB fields and associated-person requirements for businesses incorporated in the EEA+ region, in line with MiCA. Businesses outside EEA+ are unaffected. `accountPurpose` was already required for all businesses — that does not change.
EEA+ is the EEA plus the United Kingdom, Switzerland, Andorra, and several EU overseas territories (Åland Islands, French Guiana, Guadeloupe, Martinique, Mayotte, Réunion, Saint Martin). Scope is keyed off the business's registered address country (`country` on the address with `type: "registered"`). Business representatives inherit EEA+ scope from the parent business. See [EEA+ region](/concepts/onboarding/overview#eea-region) for the full definition.
### Registration number
EEA+ businesses must provide both of the following under `businessInformation` on `POST /v2/customer`:
| Field | Status | Required |
| - | - | - |
| `registrationNumber` | Existing | Required for EEA+ businesses before verification |
| `registrationNumberType` | New | Required whenever `registrationNumber` is provided, and required for EEA+ businesses before verification |
The registration number is stored and verified separately from the tax ID (`identificationNumber`). Do not submit a VAT or tax number as the registration number.
`registrationNumberType` is validated against the registered-address country. Examples:
| Country | Accepted `registrationNumberType` values |
| - | - |
| Austria | `fn` |
| Belgium | `bce`, `kbo` |
| Denmark | `cvr` |
| France | `siren`, `siret` |
| Germany | `crn` |
| Ireland | `cro` |
| Italy | `cf` |
| Netherlands | `kvk` |
| Norway | `org`, `orgnr` |
| Sweden | `org`, `orgnr` |
| Poland | `krs` |
| Portugal | `nipc` |
| Spain | `cif` |
| United Kingdom | `crn` |
| Countries with no defined type set | `other` |
Validation behavior:
* An EEA+ business missing `registrationNumber` or `registrationNumberType` cannot be submitted for verification. Submitting returns **422**.
* A `registrationNumberType` that is not valid for the registered-address country returns **422** listing the allowed types.
* Non-EEA businesses may continue to supply `registrationNumber` without a type. That behavior is unchanged.
The outstanding item surfaces as the `registration_number` requirement code on `kyb_profile_a` (EEA+ only). Sphere excludes this code for businesses outside EEA+.
You can backfill existing EEA+ businesses with `PATCH /v2/customer/{id}`. Validation runs on the merged customer state.
### Business representatives
EEA+ businesses have a broader associated-person scope than businesses outside EEA+.
**Director role.** `representationDetails.roles` accepts `director` alongside `ubo`, with up to two distinct roles.
**Ownership percentage.** `ownershipPercentage` accepts any integer from 0 to 100 (the previous minimum was 25). If the `ubo` role is present, ownership must still be ≥ 25. A representative without the `ubo` role — for example, director-only — may hold 0–24.
Call `POST /v2/business-representative` with the new role values:
```json theme={"dark"}
{
"representationDetails": {
"roles": ["ubo", "director"],
"ownershipPercentage": "40",
"isControlPerson": true,
"isSigner": true,
"title": "Managing Director"
}
}
```
The example highlights the new role and ownership values. Include the other representative fields required at creation (`customerId`, `type`, `email`, `phone`, `address`, and `personalInformation`) as usual.
**Country of birth and nationality.** For EEA+ businesses, each representative must provide `countryOfBirth` and `nationality` under `personalInformation`. Until they are supplied, they surface as `country_of_birth` and `nationality` on the representative's `ubo_kyc_profile_a` profile. Sphere excludes these codes for representatives of businesses outside EEA+.
You can backfill existing representatives with `PATCH /v2/business-representative/{id}`. Validation runs on the merged representative state.
### Business-level requirement codes
These codes appear on the business customer's `kyb_profile_a` profile for EEA+ businesses only. Sphere excludes them for businesses outside EEA+.
| Code | Meaning |
| - | - |
| `registration_number` | Provide `businessInformation.registrationNumber` and `registrationNumberType` |
| `eea_control_person_present` | At least one representative with `isControlPerson: true` |
| `eea_ap_kyc_scope` | At least one director on file, and every director, signer, control person, and ≥ 25% owner completes full identity verification |
For businesses outside EEA+, associated-person KYC scope is unchanged: only 25%+ owners.
***
## What's next
Once the business customer's verification profile is `approved`, register their payment methods and initiate a transfer.
Register a bank account so the business can send or receive funds via bank rail.
Register a crypto wallet address to enable on-ramp and off-ramp transfers.
Create and manage transfers once the customer has registered their payment methods.
Understand verification statuses, criteria arrays, and what triggers state changes.
# Supported ID and Tax ID Types
Source: https://docs.spherepay.co/concepts/onboarding/identification-types
Reference of identity document types and country-specific tax ID number types accepted for individual and business customer onboarding via SpherePay.
When onboarding customers, the [Customers API](/api-reference/customer/post) and [Documents API](/api-reference/document/post) accept a fixed set of identity **document types** and country-specific **tax identification number types**. This page is the reference for both.
These values feed identity verification (KYC/KYB). The accepted tax ID type depends on the customer's country of residence (or country of incorporation, for businesses). When a country is not listed, any tax ID type is accepted as a fallback.
Company registration numbers (`businessInformation.registrationNumber` and `registrationNumberType`) are stored and verified separately from the tax ID. Do not submit a VAT or tax number as the registration number. See [EEA+ businesses](/concepts/onboarding/business-kyb#eea-businesses) for accepted registration number types.
## Identity document types
Government-issued ID documents are uploaded via `POST /v2/document` using the `documentType` field. Accepted ID document types depend on the upload `target`:
| Document type | `documentType` value | Individual customer | Business representative |
| - | - | :-: | :-: |
| Driver's license | `drivers` | ✓ | ✓ |
| National ID card | `id_card` | ✓ | ✓ |
| Passport | `passport` | ✓ | ✓ |
| Residence permit | `residence_permit` | ✓ | ✓ |
| Digital driver's license (Brazil) | `br_digital_drivers` | ✓ | ✓ |
| Digital national ID (Brazil) | `br_digital_id` | ✓ | ✓ |
`br_digital_drivers` and `br_digital_id` are accepted only with `country: "BRA"`. They are single-sided: upload one image and omit the `side` field. These types cover Brazilian digital documents (`CNH digital`, `RG digital`). Physical Brazilian documents continue to use `drivers` and `id_card` with front and back.
Supporting (non-ID) documents use the same field: `liveness_report_document` (liveness/face verification, individuals and business representatives), `proof_of_address_document` (business representatives), and the business-customer documents `incorporation_cert_document`, `shareholder_registry_document`, and `proof_of_address_document`. See the [Upload a document](/api-reference/document/post) reference for the full per-target list.
Accepted ID document types can be further restricted by the issuing country. Always send the issuing-country ISO 3166-1 alpha-3 code in the `country` field; if a document type is not enabled for that country, the upload is rejected.
## Tax identification number types
When you provide tax identification details on a customer, all of `number`, `type`, and `country` are required together. The `type` is one of the codes below; which codes are valid depends on the country.
* **Individual customers** — `personalInformation.taxIdentification.type` (e.g. `"ssn"`, `"cpf"`).
* **Business customers** — the business tax ID type for the country of incorporation (e.g. `"ein"`, `"cnpj"`).
**United States:** the individual tax ID type is always treated as `ssn` and a tax identification number is required. **Other countries:** if the country is not in the tables below, any tax ID type string is accepted (`allowAnyTaxIdType` fallback).
### Common examples
| Country | ISO‑3 | Individual tax ID type(s) | Business tax ID type(s) |
| - | - | - | - |
| United States of America | `USA` | `itin`, `ssn` | `ein` |
| Brazil | `BRA` | `cpf` | `cnpj`, `vat` |
| Mexico | `MEX` | `curp`, `ine`, `rfc` | `rfc`, `vat` |
| United Kingdom of Great Britain and Northern Ireland | `GBR` | `nino`, `utr` | `crn`, `vat` |
| Canada | `CAN` | `sin` | `bin`, `vat` |
| Germany | `DEU` | `steuer_id` | `ust_idnr`, `crn` |
| France | `FRA` | `spi` | `siren`, `siret` |
| India | `IND` | `pan` | `gstin`, `cin`, `pan`, `vat` |
| Singapore | `SGP` | `fin`, `nric` | `uen` |
| Australia | `AUS` | `tfn` | `abn`, `acn`, `arbn` |
| Netherlands | `NLD` | `bsn` | `kvk`, `vat` |
| United Arab Emirates | `ARE` | `emirates_id` | `trn`, `vat` |
### Tax ID type glossary
Full names for every tax ID type code referenced in the tables on this page.
| Code | Name |
| - | - |
| `abn` | Australian Business Number |
| `acn` | Australian Company Number |
| `ahv` | Old Age and Survivors Insurance Number |
| `ak` | Personal Code |
| `aom` | Tax Registry Number |
| `arbn` | Australian Registered Body Number |
| `avs` | AHV Number |
| `bce` | Crossroads Bank for Enterprises Number |
| `bin` | Business Identification Number |
| `bir` | Business Identification Number |
| `bp` | Business Partner Number |
| `brn` | Business Registration Number |
| `bsn` | Burgerservicenummer (Citizen Service Number) |
| `bvn` | Bank Verification Number |
| `cac` | Corporate Affairs Commission Number |
| `cc` | Company Code |
| `cdi` | Código de Identificación |
| `cedula_juridica` | Cédula Jurídica |
| `cf` | Codice Fiscale (Tax Code) |
| `ci` | Cédula de identidad |
| `cif` | Código de Identificación Fiscal |
| `cin` | Corporate Identification Number |
| `cn` | Company Number |
| `cnp` | Cod Numeric Personal |
| `cnpj` | Cadastro Nacional da Pessoa Jurídica |
| `cpf` | Cadastro de Pessoas Físicas |
| `cpr` | Central Population Registry Number |
| `crc` | Company Registration Number |
| `crib` | Chamber of Commerce Registration Number |
| `crn` | Company Registration Number |
| `cro` | Companies Registration Office Number |
| `cui` | Unique Identification Code |
| `cuil` | Código Único de Identificación Laboral |
| `cuit` | Clave Única de Identificación Tributaria |
| `curp` | Clave Única de Registro de Población |
| `cvr` | Central Business Register Number |
| `dni` | Documento Nacional de Identidad |
| `edrpou` | Unified State Register Code |
| `ein` | Employer Identification Number |
| `embg` | Unique Identification Number |
| `emirates_id` | National Identity Card |
| `en` | Enterprise Number |
| `fin` | Foreign Identification Number |
| `fn` | Company Register Number (Firmenbuchnummer) |
| `gst` | Goods and Services Tax Number |
| `gstin` | GST Identification Number |
| `hetu` | Finnish Personal Identity Code |
| `hkid` | Hong Kong Identity Card Number |
| `hn` | Registration Number |
| `ic` | Identification Number (IČO) |
| `ico` | Identification Number (IČO) |
| `id` | Identification Number |
| `id_broj` | Identification Number |
| `idno` | Identification Number |
| `idnp` | Identification Number of the Person |
| `if` | Identifiant Fiscal |
| `iin` | Individual Identification Number |
| `ik` | Individual Code |
| `ine` | Instituto Nacional Electoral |
| `inn` | Taxpayer Identification Number |
| `ird` | Inland Revenue Department Number |
| `itin` | Individual Taxpayer Identification Number |
| `itr` | Income Tax Reference Number |
| `jmbg` | Unique Master Citizen Number |
| `kbo` | Crossroads Bank for Enterprises Number |
| `krs` | National Court Register Number |
| `kvk` | Chamber of Commerce Number |
| `maticna` | Company Registration Number |
| `matricule` | Matricule Number |
| `mf` | Matricule Fiscal |
| `mn` | My Number (Individual Number) |
| `mst` | Tax Code (Mã Số Thuế) |
| `nic` | National Identity Card Number |
| `nicn` | National Identity Card Number |
| `nie` | Número de Identificación de Extranjeros |
| `nif` | Número de Identificação Fiscal |
| `nin` | National Identification Number |
| `ninea` | National Business Identification Number |
| `nino` | National Insurance Number |
| `nip` | Tax Identification Number (NIP) |
| `nipc` | Número de Identificação de Pessoa Coletiva |
| `nipt` | Taxpayer Identification Number |
| `nit` | Número de Identificación Tributaria |
| `npwp` | Nomor Pokok Wajib Pajak |
| `nric` | National Registration Identity Card |
| `nrn` | National Register Number |
| `nrt` | Tax Registration Number |
| `ntn` | National Tax Number |
| `nuit` | Número Único de Identificação Tributária |
| `ogrn` | Primary State Registration Number |
| `oib` | Personal Identification Number |
| `orgnr` | Organization Number |
| `other` | Other (provide a description) |
| `pan` | Permanent Account Number |
| `partita_iva` | Partita IVA (VAT Number) |
| `pesel` | Personal Identification Number |
| `pib` | Tax Identification Number |
| `pin` | Tax Identification Number |
| `pk` | Person’s Code |
| `ppsn` | Personal Public Service Number |
| `qid` | Qatar ID |
| `rc` | Registration Code |
| `rccm` | Trade and Personal Property Credit Register Number |
| `rfc` | Registro Federal de Contribuyentes |
| `ricn` | Resident Identity Card Number |
| `rif` | Registro de Información Fiscal |
| `rn` | Registration Number |
| `rnc` | Registro Nacional del Contribuyente |
| `rnokpp` | Registration Number of the Taxpayer |
| `rp` | Iqama (Residency Permit) |
| `rrn` | Resident Registration Number |
| `rtn` | Registro Tributario Nacional |
| `ruc` | Registro Único de Contribuyentes |
| `rut` | Rol Único Tributario |
| `si` | Social Insurance Number |
| `sin` | Social Insurance Number |
| `siren` | SIREN Number |
| `siret` | SIRET Number |
| `spi` | Social Security Number |
| `ssm` | Companies Commission of Malaysia Number |
| `ssn` | Social Security Number |
| `steuer_id` | Steueridentifikationsnummer (Tax Identification Number) |
| `tckn` | Turkish Citizenship Number |
| `tfn` | Tax File Number |
| `tin` | Tax Identification Number |
| `tpin` | Taxpayer Identification Number |
| `trn` | Tax Registration Number |
| `ucn` | Unified Civil Number |
| `uen` | Unique Entity Number |
| `uic` | Unified Identification Code |
| `uid` | Unique Identification Number |
| `usc` | Unified Social Credit Code |
| `ust_idnr` | VAT Identification Number (USt-IdNr.) |
| `utr` | Unique Taxpayer Reference Number |
| `vat` | VAT Number |
| `vkn` | Tax Number (Vergi Kimlik Numarası) |
| `voen` | State Taxpayer Identification Number |
| `y_tunnus` | Business ID (Y-tunnus) |
### Full country reference
| Country | ISO‑3 | Supported tax ID type(s) |
| - | - | - |
| Afghanistan | `AFG` | `tin` |
| Albania | `ALB` | `tin` |
| Algeria | `DZA` | `nif` |
| Andorra | `AND` | `other` |
| Angola | `AGO` | `nif` |
| Argentina | `ARG` | `cdi`, `cuil`, `dni` |
| Armenia | `ARM` | `tin` |
| Aruba | `ABW` | `other` |
| Australia | `AUS` | `tfn` |
| Austria | `AUT` | `si` |
| Azerbaijan | `AZE` | `voen` |
| Bahamas | `BHS` | `other` |
| Bahrain | `BHR` | `cpr` |
| Bangladesh | `BGD` | `tin` |
| Barbados | `BRB` | `nrn` |
| Belgium | `BEL` | `nrn` |
| Belize | `BLZ` | `other` |
| Bermuda | `BMU` | `other` |
| Bolivia (Plurinational State of) | `BOL` | `nit` |
| Bosnia and Herzegovina | `BIH` | `jmbg` |
| Botswana | `BWA` | `tin` |
| Brazil | `BRA` | `cpf` |
| Bulgaria | `BGR` | `ucn` |
| Cambodia | `KHM` | `tin` |
| Cameroon | `CMR` | `nif` |
| Canada | `CAN` | `sin` |
| Cayman Islands | `CYM` | `other` |
| Chile | `CHL` | `rut` |
| China | `CHN` | `ricn` |
| Christmas Island | `CXR` | `tfn` |
| Colombia | `COL` | `nit`, `rut` |
| Comoros | `COM` | `nif` |
| Costa Rica | `CRI` | `tin` |
| Croatia | `HRV` | `oib` |
| Curaçao | `CUW` | `crib` |
| Cyprus | `CYP` | `tin` |
| Czechia | `CZE` | `rc` |
| Côte d’Ivoire | `CIV` | `nif` |
| Denmark | `DNK` | `cpr` |
| Dominica | `DMA` | `tin` |
| Dominican Republic | `DOM` | `tin` |
| Ecuador | `ECU` | `ruc` |
| Egypt | `EGY` | `tin` |
| El Salvador | `SLV` | `nit` |
| Estonia | `EST` | `ik` |
| Ethiopia | `ETH` | `tin` |
| Fiji | `FJI` | `tin` |
| Finland | `FIN` | `hetu` |
| France | `FRA` | `spi` |
| Georgia | `GEO` | `tin` |
| Germany | `DEU` | `steuer_id` |
| Ghana | `GHA` | `tin` |
| Gibraltar | `GIB` | `crc` |
| Greece | `GRC` | `aom` |
| Guatemala | `GTM` | `nit` |
| Haiti | `HTI` | `nif` |
| Honduras | `HND` | `rtn` |
| Hong Kong | `HKG` | `hkid` |
| Hungary | `HUN` | `tin` |
| Iceland | `ISL` | `tin` |
| India | `IND` | `pan` |
| Indonesia | `IDN` | `npwp` |
| Iraq | `IRQ` | `tin` |
| Ireland | `IRL` | `ppsn` |
| Isle of Man | `IMN` | `nino` |
| Israel | `ISR` | `tin` |
| Italy | `ITA` | `cf` |
| Jamaica | `JAM` | `trn` |
| Japan | `JPN` | `mn` |
| Jordan | `JOR` | `tin` |
| Kazakhstan | `KAZ` | `iin` |
| Kenya | `KEN` | `pin` |
| Korea (Republic of) | `KOR` | `rrn` |
| Kuwait | `KWT` | `tin` |
| Kyrgyzstan | `KGZ` | `inn` |
| Lao People’s Democratic Republic | `LAO` | `tin` |
| Latvia | `LVA` | `pk` |
| Lebanon | `LBN` | `other` |
| Liberia | `LBR` | `tin` |
| Lithuania | `LTU` | `ak` |
| Luxembourg | `LUX` | `matricule` |
| Madagascar | `MDG` | `nif` |
| Malawi | `MWI` | `tpin` |
| Malaysia | `MYS` | `itr` |
| Malta | `MLT` | `tin` |
| Marshall Islands | `MHL` | `other` |
| Mauritania | `MRT` | `nif` |
| Mauritius | `MUS` | `nicn` |
| Mexico | `MEX` | `curp`, `ine`, `rfc` |
| Moldova (Republic of) | `MDA` | `idnp` |
| Monaco | `MCO` | `other` |
| Montenegro | `MNE` | `jmbg` |
| Morocco | `MAR` | `if` |
| Mozambique | `MOZ` | `nuit` |
| Namibia | `NAM` | `tin` |
| Nepal | `NPL` | `pan` |
| Netherlands | `NLD` | `bsn` |
| New Zealand | `NZL` | `ird` |
| Nicaragua | `NIC` | `ruc` |
| Nigeria | `NGA` | `bvn`, `nin`, `tin` |
| North Macedonia | `MKD` | `embg` |
| Norway | `NOR` | `fn` |
| Oman | `OMN` | `tin` |
| Pakistan | `PAK` | `ntn` |
| Panama | `PAN` | `ruc` |
| Paraguay | `PRY` | `ruc` |
| Peru | `PER` | `ruc` |
| Philippines | `PHL` | `tin` |
| Poland | `POL` | `pesel` |
| Portugal | `PRT` | `nif` |
| Puerto Rico | `PRI` | `ssn` |
| Qatar | `QAT` | `qid` |
| Romania | `ROU` | `cnp` |
| Rwanda | `RWA` | `tin` |
| Saint Kitts and Nevis | `KNA` | `other` |
| Saint Lucia | `LCA` | `tin` |
| Saint Vincent and the Grenadines | `VCT` | `tin` |
| Samoa | `WSM` | `other` |
| Saudi Arabia | `SAU` | `rp`, `tin` |
| Senegal | `SEN` | `tin` |
| Serbia | `SRB` | `jmbg` |
| Seychelles | `SYC` | `tin` |
| Singapore | `SGP` | `fin`, `nric` |
| Slovakia | `SVK` | `rc` |
| Slovenia | `SVN` | `tin` |
| Somalia | `SOM` | `tin` |
| South Africa | `ZAF` | `itr` |
| Spain | `ESP` | `nie`, `nif` |
| Sri Lanka | `LKA` | `nic` |
| Suriname | `SUR` | `tin` |
| Sweden | `SWE` | `tin` |
| Switzerland | `CHE` | `ahv`, `avs` |
| Taiwan, Province of China | `TWN` | `tin` |
| Tanzania, United Republic of | `TZA` | `tin` |
| Thailand | `THA` | `tin` |
| Togo | `TGO` | `nif` |
| Trinidad and Tobago | `TTO` | `bir` |
| Tunisia | `TUN` | `mf` |
| Turks and Caicos Islands | `TCA` | `other` |
| Türkiye | `TUR` | `tckn` |
| Uganda | `UGA` | `tin` |
| Ukraine | `UKR` | `rnokpp` |
| United Arab Emirates | `ARE` | `emirates_id` |
| United Kingdom of Great Britain and Northern Ireland | `GBR` | `nino`, `utr` |
| United States of America | `USA` | `itin`, `ssn` |
| Uruguay | `URY` | `ci`, `rut` |
| Uzbekistan | `UZB` | `inn` |
| Vanuatu | `VUT` | `other` |
| Venezuela (Bolivarian Republic of) | `VEN` | `rif` |
| Viet Nam | `VNM` | `mst` |
| Virgin Islands (British) | `VGB` | `other` |
| Yemen | `YEM` | `tin` |
| Zambia | `ZMB` | `tpin` |
| Zimbabwe | `ZWE` | `tin` |
| Country | ISO‑3 | Supported tax ID type(s) |
| - | - | - |
| Afghanistan | `AFG` | `tin` |
| Albania | `ALB` | `nipt` |
| Algeria | `DZA` | `nif` |
| Andorra | `AND` | `nrt` |
| Angola | `AGO` | `nif` |
| Argentina | `ARG` | `cuit` |
| Armenia | `ARM` | `tin` |
| Aruba | `ABW` | `cn` |
| Australia | `AUS` | `abn`, `acn`, `arbn` |
| Austria | `AUT` | `fn`, `uid` |
| Azerbaijan | `AZE` | `voen` |
| Bahamas | `BHS` | `tin` |
| Bahrain | `BHR` | `crn` |
| Bangladesh | `BGD` | `bin`, `vat` |
| Barbados | `BRB` | `tin` |
| Belgium | `BEL` | `bce`, `kbo` |
| Belize | `BLZ` | `tin` |
| Bermuda | `BMU` | `crn` |
| Bolivia (Plurinational State of) | `BOL` | `nit`, `vat` |
| Bosnia and Herzegovina | `BIH` | `id_broj` |
| Botswana | `BWA` | `crn` |
| Brazil | `BRA` | `cnpj`, `vat` |
| Bulgaria | `BGR` | `uic` |
| Cambodia | `KHM` | `tin`, `vat` |
| Cameroon | `CMR` | `nif`, `rccm` |
| Canada | `CAN` | `bin`, `vat` |
| Cayman Islands | `CYM` | `crn` |
| Chile | `CHL` | `rut` |
| China | `CHN` | `usc` |
| Christmas Island | `CXR` | `abn` |
| Colombia | `COL` | `nit`, `rut` |
| Comoros | `COM` | `nif` |
| Costa Rica | `CRI` | `cedula_juridica` |
| Croatia | `HRV` | `oib` |
| Curaçao | `CUW` | `crib` |
| Cyprus | `CYP` | `crn` |
| Czechia | `CZE` | `ico` |
| Côte d’Ivoire | `CIV` | `nif` |
| Denmark | `DNK` | `cvr` |
| Dominica | `DMA` | `tin` |
| Dominican Republic | `DOM` | `rnc` |
| Ecuador | `ECU` | `ruc` |
| Egypt | `EGY` | `tin`, `vat` |
| Estonia | `EST` | `rc` |
| Ethiopia | `ETH` | `tin` |
| Fiji | `FJI` | `tin` |
| Finland | `FIN` | `y_tunnus` |
| France | `FRA` | `siren`, `siret` |
| Georgia | `GEO` | `ic` |
| Germany | `DEU` | `ust_idnr`, `crn` |
| Ghana | `GHA` | `tin` |
| Gibraltar | `GIB` | `crn` |
| Greece | `GRC` | `aom` |
| Guatemala | `GTM` | `nit`, `vat` |
| Haiti | `HTI` | `nif` |
| Honduras | `HND` | `rtn` |
| Hong Kong | `HKG` | `brn` |
| Hungary | `HUN` | `tin` |
| Iceland | `ISL` | `id` |
| India | `IND` | `gstin`, `cin`, `pan`, `vat` |
| Indonesia | `IDN` | `npwp`, `vat` |
| Iraq | `IRQ` | `tin` |
| Ireland | `IRL` | `cro` |
| Isle of Man | `IMN` | `crn` |
| Israel | `ISR` | `crn` |
| Italy | `ITA` | `partita_iva`, `cf` |
| Jamaica | `JAM` | `trn` |
| Japan | `JPN` | `hn` |
| Jordan | `JOR` | `tin` |
| Kazakhstan | `KAZ` | `bin` |
| Kenya | `KEN` | `pin`, `vat` |
| Korea (Republic of) | `KOR` | `brn` |
| Kuwait | `KWT` | `crn` |
| Kyrgyzstan | `KGZ` | `inn` |
| Lao People’s Democratic Republic | `LAO` | `tin` |
| Latvia | `LVA` | `rn` |
| Lebanon | `LBN` | `crn` |
| Liberia | `LBR` | `tin` |
| Lithuania | `LTU` | `cc` |
| Luxembourg | `LUX` | `matricule` |
| Madagascar | `MDG` | `nif` |
| Malawi | `MWI` | `tpin` |
| Malaysia | `MYS` | `ssm`, `gstin` |
| Malta | `MLT` | `crn`, `vat` |
| Marshall Islands | `MHL` | `en` |
| Mauritania | `MRT` | `nif` |
| Mauritius | `MUS` | `brn` |
| Mexico | `MEX` | `rfc`, `vat` |
| Moldova (Republic of) | `MDA` | `idno` |
| Monaco | `MCO` | `crn` |
| Montenegro | `MNE` | `crn` |
| Morocco | `MAR` | `if` |
| Mozambique | `MOZ` | `pib` |
| Namibia | `NAM` | `vat` |
| Nepal | `NPL` | `pan` |
| Netherlands | `NLD` | `kvk`, `vat` |
| New Zealand | `NZL` | `gst` |
| Nicaragua | `NIC` | `ruc` |
| Nigeria | `NGA` | `cac`, `tin`, `vat` |
| North Macedonia | `MKD` | `tin` |
| Norway | `NOR` | `orgnr` |
| Oman | `OMN` | `crn` |
| Pakistan | `PAK` | `ntn` |
| Panama | `PAN` | `ruc` |
| Peru | `PER` | `ruc` |
| Philippines | `PHL` | `tin` |
| Poland | `POL` | `nip`, `krs` |
| Portugal | `PRT` | `nif`, `nipc` |
| Qatar | `QAT` | `crn` |
| RUS | `RUS` | `inn`, `ogrn` |
| Romania | `ROU` | `cui` |
| Saint Kitts and Nevis | `KNA` | `tin` |
| Saint Lucia | `LCA` | `tin` |
| Saudi Arabia | `SAU` | `crn` |
| Senegal | `SEN` | `ninea` |
| Singapore | `SGP` | `uen` |
| Slovakia | `SVK` | `ico` |
| Slovenia | `SVN` | `maticna` |
| South Africa | `ZAF` | `vat`, `tin` |
| Spain | `ESP` | `nif`, `cif` |
| Sri Lanka | `LKA` | `brn` |
| Sweden | `SWE` | `orgnr` |
| Switzerland | `CHE` | `uid` |
| Thailand | `THA` | `tin` |
| Tunisia | `TUN` | `mf` |
| Türkiye | `TUR` | `vkn` |
| Uganda | `UGA` | `tin` |
| Ukraine | `UKR` | `edrpou` |
| United Arab Emirates | `ARE` | `trn`, `vat` |
| United Kingdom of Great Britain and Northern Ireland | `GBR` | `crn`, `vat` |
| United States of America | `USA` | `ein` |
| Uruguay | `URY` | `rut` |
| Uzbekistan | `UZB` | `inn` |
| Venezuela (Bolivarian Republic of) | `VEN` | `rif` |
| Viet Nam | `VNM` | `mst` |
| Yemen | `YEM` | `tin` |
| Zambia | `ZMB` | `tpin` |
| Zimbabwe | `ZWE` | `bp` |
# KYC for Individual Customers
Source: https://docs.spherepay.co/concepts/onboarding/individual-kyc
Onboard individual customers with KYC identity verification. Choose API or hosted link, with Sphere-Managed or Platform-Managed compliance.
KYC (Know Your Customer) is a legal identity verification process required before any individual customer can send or receive funds through SpherePay. It is mandated by anti-money laundering (AML) regulations and must be completed once per customer. You choose how to integrate — via API or hosted link — and which compliance model to use.
## Required information
SpherePay requires the following information to verify an individual customer's identity. Fields marked as **API** are submitted in the customer creation request; fields marked as **ID Document** are extracted automatically from the uploaded document.
| Field | Source | Notes |
| - | - | - |
| Email address | API | `email` field in the customer creation request |
| Phone number | API | `phone` field in the customer creation request |
| Country of residence | API | `address.country` in the customer creation request |
| Residential address | API | Full `address` object (street, city, postal code, state) |
| Tax identification number | API | `personalInformation.taxIdentificationNumber` — accepted types vary by country |
| First name & last name | ID Document | Auto-extracted from the uploaded government-issued ID |
| Date of birth | ID Document | Auto-extracted from the uploaded government-issued ID |
| Government-issued ID document | ID Document | Uploaded via `POST /v2/document` — passport, ID card, or driver's license |
| Proof of address | ID Document | Uploaded via `POST /v2/document` |
Individual customers resident in the EEA+ region must also provide `accountPurpose`, `countryOfBirth`, and `nationality` before verification. See [EEA+ residents](#eea-residents).
In certain circumstances SpherePay may request additional information, such as for politically exposed persons (PEPs), elderly customers, high-risk profiles, or customers with unusually high expected transaction volumes. SpherePay will contact you directly when this applies.
## KYC flow overview
The flow below shows the steps required for individual KYC. The only step that differs between onboarding models is TOS acceptance — it is handled in-flow in Sphere-Managed and embedded in your platform's terms in Platform-Managed.
1. Create customer with full `personalInformation` and `address`
2. Generate TOS link → redirect customer to accept terms
3. Upload government-issued ID document
4. Upload proof of address document
5. Complete face liveness verification (Sumsub SDK, in-flow)
6. Poll `GET /v2/customer/{id}` until `status` reaches `approved`
1. Create customer with full `personalInformation` and `address`
2. Upload government-issued ID document
3. Upload proof of address document
4. Upload liveness report document from your KYC provider
5. Poll `GET /v2/customer/{id}` until `status` reaches `approved`
## Choose an integration method
You must pick a single integration method and onboarding model for each customer. Mixing methods for the same customer — or falling back to one if the other fails — is not supported and will result in an unsuccessful onboarding.
Default model. Sphere handles TOS, face liveness, and OTP via hosted links.
Opt-in model. Upload a liveness report document. No hosted redirects required.
Fastest integration. SpherePay hosts the entire verification experience.
***
## KYC via API — Sphere-Managed
This is the default model for all new API integrations. Sphere handles compliance steps in-flow using hosted links and redirects: Terms of Service acceptance, face liveness verification, and OTP contact verification.
**Before you begin**, ensure you have:
* A SpherePay API key
* The customer's personal information (full address, tax ID)
* A copy of the customer's government-issued identity document
If your application is enabled for more than one verification profile, choose the customer's profiles at creation with `enabledVerificationProfiles`. See [Onboard a customer with verification profiles](/concepts/onboarding/verification-profiles/onboard-new-customer).
Call `POST /v2/customer` with `type: "individual"` and include the full `personalInformation` and `address` objects. These fields allow SpherePay to verify the customer's identity programmatically.
```json theme={"dark"}
{
"type": "individual",
"email": "jane.smith@example.com",
"phone": "+14155550123",
"address": {
"line1": "233 South Wacker Drive",
"line2": "Suite 4700",
"city": "Chicago",
"postalCode": "60606",
"state": "IL",
"country": "USA"
},
"personalInformation": {
"taxIdentificationNumber": "123456789",
"taxIdentificationNumberType": "ssn",
"taxIdentificationNumberCountry": "USA"
}
}
```
The `personalInformation` object accepts the following fields:
| Field | Description |
| - | - |
| `taxIdentificationNumberCountry` | The customer's country code (e.g. `USA`, `SGP`, `GBR`) |
| `taxIdentificationNumberType` | The ID number type, e.g. `ssn` (USA), `nric` (Singapore), `nino` (UK) |
| `taxIdentificationNumber` | The actual identification number |
| `taxIdentificationNumberDescription` | Human-readable description — only required when type is `other` |
| `accountPurpose` | Why the customer is opening the account. Required for EEA+ residents before verification — see [EEA+ residents](#eea-residents) |
| `countryOfBirth` | ISO 3166-1 alpha-3 country of birth. Required for EEA+ residents before verification |
| `nationality` | ISO 3166-1 alpha-3 nationality. Required for EEA+ residents before verification |
Generate a TOS link and redirect the customer to accept the Terms and Conditions and Privacy Policy.
```bash theme={"dark"}
POST https://api.spherepay.co/v2/customer/{id}/tos-link
```
This step can be done in parallel with uploading documents.
Upload the customer's government-issued identity document using `POST /v2/document`.
```bash theme={"dark"}
curl --location 'https://api.spherepay.co/v2/document' \
--header 'Authorization: Bearer ' \
--form 'target="customer"' \
--form 'targetId="customer_4914a2f6226e42cc8d207ead9573b29f"' \
--form 'documentType="id_card"' \
--form 'side="front"' \
--form 'file=@"/path/to/your/document.jpg"' \
--form 'country="SGP"'
```
Upload both the front and back for ID cards and driver's licenses. Passports require front only (with country-specific exceptions). See the Document Guideline for accepted formats by country.
Check the customer's `required` array via `GET /v2/customer/{id}`. Then perform **exactly one** of the following, depending on what appears in `required`:
* **`liveness_check` in required** — Generate a face verification link via `POST /v2/enhanced-due-diligence/face-verification-link` and redirect the customer to complete an interactive liveness check via the Sumsub SDK.
* **`liveness_report_document` in required** — Upload a liveness report document from your identity verification provider via `POST /v2/document` with `documentType: "liveness_report"`.
Do not perform both paths. Check the `required` array and follow only the matching option.
Once all required steps are complete, SpherePay processes the verification automatically — no submit call is needed. Poll `GET /v2/customer/{id}` until `status` in `verificationProfiles` reaches `approved`.
```bash theme={"dark"}
GET https://api.spherepay.co/v2/customer/{id}
```
```json theme={"dark"}
{
"id": "customer_f31121c389624d3697cbf3ea8830b7a4",
"email": "jane.smith@example.com",
"phone": "+14155550123",
"verificationProfiles": [
{
"name": "kyc_profile_a",
"status": "approved",
"criteria": {
"complete": [
"email_address",
"phone_number",
"residential_address",
"tax_identification_number",
"identity_document",
"liveness_report_document"
],
"pending": [],
"required": [],
"errors": []
}
}
],
"createdAt": "2026-03-09T20:46:31.305Z",
"updatedAt": "2026-03-09T20:46:31.305Z",
"type": "individual"
}
```
When `required` is empty and `status` is `approved`, the customer is fully onboarded and ready to transfer.
KYC review typically takes 0–2 business days after all required documents and data are submitted.
***
## KYC via API — Platform-Managed
This opt-in model is for platforms that already perform KYC, collect liveness verification, and embed Sphere's Terms of Service. Your platform handles compliance upstream — no hosted redirects are required.
Platform-Managed is not available by default. Your platform must meet qualification requirements and receive approval from Sphere's Compliance team before going live. Contact your dedicated Solutions Engineer to initiate the approval process.
**Additional prerequisites beyond the standard set:**
* A liveness report document from your identity verification provider (e.g. Sumsub, Persona)
* Platform-Managed onboarding approved for your application by Sphere Compliance
Call `POST /v2/customer` with `type: "individual"`, full `personalInformation`, and `address`. The request body is identical to the Sphere-Managed path.
```json theme={"dark"}
{
"type": "individual",
"email": "jane.smith@example.com",
"phone": "+14155550123",
"address": {
"line1": "233 South Wacker Drive",
"line2": "Suite 4700",
"city": "Chicago",
"postalCode": "60606",
"state": "IL",
"country": "USA"
},
"personalInformation": {
"taxIdentificationNumber": "123456789",
"taxIdentificationNumberType": "ssn",
"taxIdentificationNumberCountry": "USA"
}
}
```
Upload the customer's identity documents via `POST /v2/document`. Repeat for each required document type.
```bash theme={"dark"}
curl --location 'https://api.spherepay.co/v2/document' \
--header 'Authorization: Bearer ' \
--form 'target="customer"' \
--form 'targetId="customer_4914a2f6226e42cc8d207ead9573b29f"' \
--form 'documentType="id_card"' \
--form 'side="front"' \
--form 'file=@"/path/to/your/document.jpg"' \
--form 'country="SGP"'
```
Upload the liveness report produced by your identity verification provider. This replaces the in-flow face liveness check used in Sphere-Managed.
```bash theme={"dark"}
curl --location 'https://api.spherepay.co/v2/document' \
--header 'Authorization: Bearer ' \
--form 'target="customer"' \
--form 'targetId="customer_4914a2f6226e42cc8d207ead9573b29f"' \
--form 'documentType="liveness_report"' \
--form 'file=@"/path/to/liveness_report.pdf"'
```
Only upload this document if `liveness_report_document` appears in the `required` array of the customer's verification profile.
Once all required data and documents are submitted, SpherePay processes the KYC verification automatically. Poll `GET /v2/customer/{id}` until `status` reaches `approved`.
***
## EEA+ residents
Sphere enforces additional KYC fields for individual customers resident in the EEA+ region, in line with MiCA. Customers outside EEA+ are unaffected.
EEA+ is the EEA plus the United Kingdom, Switzerland, Andorra, and several EU overseas territories (Åland Islands, French Guiana, Guadeloupe, Martinique, Mayotte, Réunion, Saint Martin). Scope is keyed off the customer's residence country (`address.country`). See [EEA+ region](/concepts/onboarding/overview#eea-region) for the full definition.
### Additional fields
Provide these fields under `personalInformation` on `POST /v2/customer` and `PATCH /v2/customer/{id}`:
| Field | Status | Format | Required for EEA+ before verification |
| - | - | - | - |
| `accountPurpose` | New | `enum` — see values below | Yes |
| `countryOfBirth` | Existing | ISO 3166-1 alpha-3 | Yes |
| `nationality` | Existing | ISO 3166-1 alpha-3 | Yes |
Accepted `accountPurpose` values:
* `personal_or_living_expenses`
* `payments_to_friends_or_family_abroad`
* `receive_salary`
* `receive_payment_for_freelancing`
* `protect_wealth`
* `purchase_goods_and_services`
* `charitable_donations`
* `ecommerce_retail_payments`
* `investment_purposes`
* `operating_a_company`
### When Sphere enforces these fields
These fields are **not** hard-required at creation. `POST /v2/customer` and `PATCH /v2/customer/{id}` succeed without them, so you can complete a customer incrementally.
Until all three are supplied, they appear in the customer's `kyc_profile_a` criteria as `country_of_birth`, `nationality`, and `account_purpose`, and the customer cannot be submitted for verification. Sphere excludes these codes entirely for customers whose residence country is outside EEA+.
You can backfill existing EEA+ customers with `PATCH /v2/customer/{id}`. Validation runs on the merged customer state — send only the missing fields.
### Foreign tax registration
If `taxIdentificationNumberCountry` differs from the customer's residence country (`address.country`), Sphere records a foreign tax registration and forwards it to the verification provider. You do not send a separate field for this.
### Example
Call `POST /v2/customer` with the EEA+ fields under `personalInformation`:
```json theme={"dark"}
{
"type": "individual",
"firstName": "Anna",
"lastName": "Lindqvist",
"address": { "country": "SWE" },
"personalInformation": {
"countryOfBirth": "FIN",
"nationality": "SWE",
"accountPurpose": "receive_salary"
}
}
```
The example highlights the EEA+ fields. Include the other customer fields required at creation (`email`, `phone`, and a full `address`) as usual.
***
## What's next
Once the customer's verification profile is `approved`, register their payment methods and initiate a transfer.
Register a bank account so the customer can send or receive funds via bank rail.
Register a crypto wallet address to enable on-ramp and off-ramp transfers.
Create and manage transfers once the customer has registered their payment methods.
Understand verification statuses, criteria arrays, and what triggers state changes.
# Onboard Customers with a Hosted KYC Link
Source: https://docs.spherepay.co/concepts/onboarding/kyc-via-link
Use SpherePay's hosted onboarding link to verify customers without building a custom UI. Redirect customers to complete KYC or KYB in minutes.
The hosted link integration is the fastest way to onboard customers in SpherePay. Instead of building a custom verification UI, you create a customer via the API, retrieve a hosted link, and redirect your customer to complete their full verification on a SpherePay-managed page. The hosted experience handles document upload, face liveness verification, and all required questions in a single flow.
This approach works for both individual customers (KYC) and business customers (KYB). Hosted verification links collect the additional EEA+ KYC and KYB data automatically when it applies — integrators using hosted links need no API changes for those requirements.
Use the hosted link approach for the fastest time to integration. It requires minimal frontend work — you only manage the initial API call and the redirect.
## When to use this approach
The hosted link is ideal when:
* You want to integrate quickly without building a custom onboarding UI
* You prefer to delegate document upload, liveness checks, and OTP to SpherePay
* You are prototyping or running an initial launch before investing in a fully custom flow
If you need complete control over the data collection experience or want to embed verification steps directly inside your product, use the [API-based integration](/concepts/onboarding/individual-kyc) instead.
Pick a single integration method for each customer. Do not mix the hosted link and API approaches for the same customer, or fall back to one if the other fails — this will result in an unsuccessful onboarding.
***
## How it works
1. You call `POST /v2/customer` to create a customer record with basic contact details.
2. You call the TOS link endpoint in parallel to generate a Terms of Service acceptance link.
3. You call the KYC/KYB link endpoint to generate a hosted verification link.
4. You redirect or send the link to your customer.
5. The customer completes the entire verification process on the hosted page.
6. You poll `GET /v2/customer/{id}` to detect when the verification profile reaches `approved`.
***
## Step-by-step guide
Call `POST /v2/customer` with the customer's basic contact information and address. For the hosted link path, you do not need to include `personalInformation` — the customer enters that information on the hosted page.
**For individual customers:**
```json theme={"dark"}
{
"type": "individual",
"email": "jane.smith@example.com",
"phone": "+14155550123",
"address": {
"line1": "233 South Wacker Drive",
"line2": "Suite 4700",
"city": "Chicago",
"postalCode": "60606",
"state": "IL",
"country": "USA"
}
}
```
**For business customers:**
```json theme={"dark"}
{
"type": "business",
"email": "contact@acmecorp.example.com",
"phone": "+14155551234",
"addresses": [
{
"type": "registered",
"country": "USA",
"line1": "123 Main Street",
"city": "Chicago",
"state": "IL",
"postalCode": "60601"
},
{
"type": "operating",
"country": "USA",
"line1": "456 Market Street",
"city": "Chicago",
"state": "IL",
"postalCode": "60602"
}
]
}
```
Save the `id` from the response — you'll need it in the next steps.
Generate a TOS link and redirect the customer to accept the Terms and Conditions. This step can be done in parallel with generating the KYC/KYB link.
```bash theme={"dark"}
POST https://api.spherepay.co/v2/customer/{id}/tos-link
```
Redirect the customer to the returned URL to complete TOS acceptance.
In parallel with the TOS step, generate the hosted verification link for your customer.
```bash theme={"dark"}
POST https://api.spherepay.co/v2/customer/{id}/kyc-link
```
This single endpoint works for both individual (KYC) and business (KYB) customers — SpherePay determines the correct flow based on the customer's `type`.
Once the link is generated, redirect the customer to the returned URL. The hosted experience handles:
* Document upload (ID document, proof of address)
* Face liveness verification
* OTP contact verification
* Business document upload and associated-person registration (for business customers)
* Additional EEA+ KYC/KYB fields when the customer's residence or registered address is in the EEA+ region
Hosted verification links collect the new EEA+ data automatically. Integrators using hosted links need no API changes for these requirements.
During the KYC/KYB flow, the customer may register a bank account. It will remain in `pending` status until their identity is fully verified and the verification profile reaches `approved`.
Redirect the customer directly from your application, or send them the link via email or SMS. The customer does not need an existing SpherePay account to complete the hosted verification.
After the customer completes the hosted flow, poll `GET /v2/customer/{id}` until `status` in `verificationProfiles` reaches `approved`.
```bash theme={"dark"}
GET https://api.spherepay.co/v2/customer/{id}
```
```json theme={"dark"}
{
"id": "customer_f31121c389624d3697cbf3ea8830b7a4",
"email": "jane.smith@example.com",
"phone": "+14155550123",
"verificationProfiles": [
{
"name": "kyc_profile_a",
"status": "approved",
"criteria": {
"complete": [
"email_address",
"phone_number",
"residential_address",
"tax_identification_number",
"identity_document",
"liveness_report_document"
],
"pending": [],
"required": [],
"errors": []
}
}
],
"createdAt": "2026-03-09T20:46:31.305Z",
"updatedAt": "2026-03-09T20:46:31.305Z",
"type": "individual"
}
```
When `required` is empty and `status` is `approved`, the customer is fully onboarded and ready to transfer.
Poll on a reasonable interval — for example, every 30 seconds — rather than continuously. You can also use webhooks if your integration supports them.
***
## What's next
Once the customer's verification profile is `approved`, register their payment methods and initiate a transfer.
Register a bank account so the customer can send or receive funds via bank rail.
Register a crypto wallet address to enable on-ramp and off-ramp transfers.
Create and manage transfers once the customer has registered their payment methods.
Understand verification statuses, criteria arrays, and what triggers state changes.
# Customers & Onboarding in SpherePay
Source: https://docs.spherepay.co/concepts/onboarding/overview
Before any transfer can occur, customers must be created and verified. Learn about KYC for individuals and KYB for businesses in SpherePay.
Before a customer can send or receive funds through SpherePay, they must be created and verified. SpherePay operates under anti-money laundering (AML) and know-your-customer (KYC) regulations, which means every customer who initiates or receives a transfer must have their identity confirmed before any funds move. This applies to both individuals and businesses.
## What is a customer?
In SpherePay, a **customer** is an entity — either an individual person or a registered business — that your platform onboards to send or receive funds. You create a customer record via the API, then complete the required verification steps before that customer can participate in any transfer.
A customer whose verification profile is not `approved` cannot initiate or receive transfers. Complete onboarding before building any transfer logic.
## Customer types
SpherePay supports two customer types. Choose the type that matches your end user.
| | Individual | Business |
| - | - | - |
| **Verification process** | KYC (Know Your Customer) | KYB (Know Your Business) |
| **Identity documents** | Government-issued ID, tax identification number | Incorporation certificate, shareholder registry, associated-person IDs |
| **Associated persons** | Not applicable | UBOs (≥ 25% ownership) for all businesses. EEA+ businesses also require directors, control persons, and signers — see [EEA+ businesses](/concepts/onboarding/business-kyb#eea-businesses). |
| **Typical review time** | 0–2 business days | 2–7 business days |
## EEA+ region
Sphere enforces enhanced KYC and KYB requirements for customers in the **EEA+** region, in line with MiCA. Customers outside EEA+ are unaffected. No existing fields were removed or renamed.
EEA+ is the European Economic Area (EEA) plus the United Kingdom, Switzerland, Andorra, and several EU overseas territories: Åland Islands (`ALA`), French Guiana (`GUF`), Guadeloupe (`GLP`), Martinique (`MTQ`), Mayotte (`MYT`), Réunion (`REU`), and Saint Martin (`MAF`).
Scope is keyed off:
| Customer | Country used |
| - | - |
| Individual | Residence country (`address.country`) |
| Business | Registered address country (`country` on the address with `type: "registered"`) |
| Business representative | Inherited from the parent business |
See [EEA+ residents](/concepts/onboarding/individual-kyc#eea-residents) and [EEA+ businesses](/concepts/onboarding/business-kyb#eea-businesses) for the additional fields and requirement codes.
## Onboarding models
SpherePay supports two onboarding models that determine how compliance steps — identity verification, liveness checks, legal agreements, and contact verification — are handled.
| | Sphere-Managed | Platform-Managed |
| - | - | - |
| **Availability** | Default — available to all new integrations | Opt-in — requires Sphere Compliance approval |
| **Liveness verification** | In-flow face liveness check via Sumsub SDK | Upload a liveness report from your own KYC provider |
| **[Terms of Service](https://spherepay.co/terms)** | Hosted TOS link generated by Sphere | Embedded in your platform's own terms |
| **Contact verification (OTP)** | Sphere manages phone and email OTP | Handled by your platform upstream |
| **[MSA (business only)](https://spherepay.co/legal/msa/latest)** | In-flow MSA signing via hosted link | Completed with Sphere prior to onboarding |
All new API integrations use Sphere-Managed by default. Platform-Managed onboarding is opt-in and requires qualification — contact your dedicated Solutions Engineer to initiate the approval process.
## Integration methods
Both customer types support two integration methods. Pick one and use it consistently — mixing methods for the same customer is not supported.
| | API | Hosted Link |
| - | - | - |
| **You control** | Every data collection and submission step | Only the redirect — SpherePay handles the rest |
| **Frontend effort** | High | Minimal |
| **Best for** | Custom onboarding UX embedded in your product | Fastest time to integration |
## After approval
Once a customer's verification profile status reaches `approved`, they can:
* Register a bank account to send or receive funds via bank rail
* Register a crypto wallet to enable on-ramp and off-ramp transfers
* Initiate a transfer
Onboard individuals through identity verification. Choose between API-based or hosted link integration, with Sphere-Managed or Platform-Managed compliance.
Onboard businesses with company documents, UBO registration, and identity verification via API or hosted link.
Use SpherePay's hosted onboarding link to verify customers without building a custom UI.
Understand what each verification status means and what triggers transitions between states.
# Verification Profiles
Source: https://docs.spherepay.co/concepts/onboarding/verification-profile
A verification profile tracks a customer's KYC or KYB approval state for a group of products. Learn which profiles exist, what each one requires and unlocks, and how statuses move.
A verification profile is the set of KYC or KYB requirements SpherePay needs to approve a customer — individual or business — for a group of products. Each profile unlocks its own products and assets. A customer can hold several profiles at once, and each one moves through its own statuses independently.
Profiles are returned in the `verificationProfiles` array of `GET /v2/customer/{id}`. Monitoring them is the primary way to determine whether a customer is ready to transfer on a given rail.
## Available profiles
Your application is enabled for a set of profiles by SpherePay. Each customer is evaluated against the profiles you select for them with `enabledVerificationProfiles` — see [Selecting profiles](/concepts/onboarding/verification-profiles/eligibility).
| Profile | Name in the API | Products | Rails | Assets | Eligible residents | Limitations |
| - | - | - | - | - | - | - |
| A | `kyc_profile_a` (individuals), `kyb_profile_a` (businesses), `ubo_kyc_profile_a` (business representatives) | Transfers, offloader wallets, on-ramper accounts | ACH, Wire, SEPA, stablecoin networks | USD, EUR, USDC, USDT, EURC | US and non-US residents | — |
| C | `kyc_profile_c`, `kyb_profile_c`, `ubo_kyc_profile_c` | Transfers | Wire, SWIFT, Ethereum, Solana, Tron | USD, USDC, USDT | Non-US residents only | Off-ramp only |
| D | `kyc_profile_d`, `kyb_profile_d`, `ubo_kyc_profile_d` | Transfers | SPEI | MXN | Non-US residents only | MXN to USD only; requires profile C on the same customer |
When SpherePay enables a new profile for your application, existing customers are **not** enrolled automatically. Verifying a customer for an additional profile has a cost, so you add the profile explicitly with `PATCH /v2/customer/{id}`. See [Add a profile to an existing customer](/concepts/onboarding/verification-profiles/onboard-new-customer#add-a-profile-later).
Profile selection applies to customers you onboard through the API. Customers onboarded through [hosted KYC links](/concepts/onboarding/kyc-via-link) are enrolled in your application's default profiles; `enabledVerificationProfiles` is not available on the hosted flow.
## Verification statuses
The `status` field on a verification profile has five possible values.
| Status | Description | Can transact on this profile? |
| - | - | - |
| `incomplete` | Not all requirements have been supplied. This is the initial state. `criteria.required` lists what is outstanding. | No |
| `pending` | All requirements are in and SpherePay is reviewing. | No |
| `approved` | SpherePay approved the customer for this profile. | Yes |
| `rejected` | SpherePay could not approve the customer based on the submitted information. | No |
| `resubmission_required` | SpherePay needs corrected information. `criteria.errors` lists what to fix. | No |
## Status lifecycle
```
incomplete → pending → approved
↘ rejected
↘ resubmission_required → pending
```
* The customer starts in `incomplete`. The `criteria.required` array lists all outstanding requirements for that profile.
* Once every requirement is fulfilled, SpherePay submits the customer for review and the profile moves to `pending`. No submit call is needed.
* The review completes and the profile moves to `approved`, `rejected`, or `resubmission_required`.
* Each profile moves on its own. Profile C can reach `approved` while profile A is still `pending` on the same customer.
A customer is submitted for review only once **every** enabled profile has an empty `criteria.required`. If you enable two profiles, supply the requirements for both before expecting either to leave `incomplete`.
## Verification criteria arrays
Each verification profile contains a `criteria` object with four arrays.
| Array | Description |
| - | - |
| `complete` | Requirements that have been fulfilled. No further action needed. |
| `pending` | Requirements currently being processed. Wait for this to resolve. |
| `required` | Requirements that must still be completed. You or your customer must act on these. |
| `errors` | Requirements with validation errors, as `{ name, detail }` objects. Correct these before resubmitting. |
Requirements shared between profiles are satisfied once for all of them. If a customer already holds an approved profile A, adding profile C only asks for the criteria C needs that A did not.
## How to check verification status
```bash theme={"dark"}
GET https://api.spherepay.co/v2/customer/{id}
```
```json theme={"dark"}
{
"id": "customer_f31121c389624d3697cbf3ea8830b7a4",
"enabledVerificationProfiles": ["a", "c"],
"verificationProfiles": [
{
"name": "kyc_profile_a",
"status": "approved",
"criteria": {
"complete": ["email_address", "phone_number", "residential_address", "tax_identification_number", "identity_document", "liveness_report_document", "account_purpose", "source_of_funds", "expected_monthly_payments", "employment_status", "acting_as_intermediary", "occupation_soc_code", "source_of_funds_document"],
"pending": [],
"required": [],
"errors": []
}
},
{
"name": "kyc_profile_c",
"status": "incomplete",
"criteria": {
"complete": ["email_address", "phone_number", "residential_address", "tax_identification_number", "identity_document", "liveness_report_document", "source_of_funds", "occupation_soc_code", "source_of_funds_document"],
"pending": [],
"required": ["sex", "country_of_birth", "nationality", "proof_of_address_document", "w8_ben_document"],
"errors": []
}
}
],
"type": "individual"
}
```
Poll `GET /v2/customer/{id}` to detect when a profile reaches `approved`, then proceed with payment method registration and transfers on the rails that profile unlocks.
## Webhook events
Instead of polling, subscribe to [customer webhook events](/concepts/webhooks/event-catalog#customer-events). SpherePay emits one event per verification profile per status change — `customer.pending`, `customer.approved` and `customer.rejected` — and `data.verificationProfile` names the profile that changed, for example `kyc_profile_c` or `ubo_kyc_profile_a`. A customer with two profiles produces two independent event streams, so key your handling on the profile name, not only on the customer ID.
## Requirements by profile
Each profile evaluates its own set of criteria. Expand a customer type to see which profiles evaluate each item and how to satisfy it.
Each item in the `criteria` arrays corresponds to a requirement. The columns show which profiles evaluate it.
| Item | Action required | A | C | D |
| - | - | - | - | - |
| `email_address` | Provide `email` | ✓ | ✓ | ✓ |
| `phone_number` | Provide `phone` | ✓ | ✓ | ✓ |
| `residential_address` | Provide the full `address` object | ✓ | ✓ | ✓ |
| `tax_identification_number` | Provide `personalInformation.taxIdentificationNumber` and its type and country | ✓ | ✓ | ✓ |
| `identity_document` | Upload a government-issued ID via `POST /v2/document` (`passport`, `id_card` front and back, or `drivers`) | ✓ | ✓ | ✓ |
| `liveness_report_document` | Upload a liveness report via `POST /v2/document` with `documentType: "liveness_report_document"` | ✓ | ✓ | ✓ |
| `source_of_funds_document` | Upload via `POST /v2/document` with `documentType: "source_of_funds_document"` | ✓ | ✓ | ✓ |
| `source_of_funds` | Provide `personalInformation.sourceOfFunds` | ✓ | ✓ | ✓ |
| `occupation_soc_code` | Provide `personalInformation.occupationSocCode` | ✓ | ✓ | ✓ |
| `account_purpose` | Provide `personalInformation.accountPurpose` (and `accountPurposeDescription` when the purpose is `other`) | ✓ | — | — |
| `employment_status` | Provide `personalInformation.employmentStatus` | ✓ | — | — |
| `acting_as_intermediary` | Provide `personalInformation.actingAsIntermediary` | ✓ | — | — |
| `expected_monthly_payments` | Provide `personalInformation.expectedMonthlyPayments` | ✓ | — | ✓ |
| `country_of_birth` | Provide `personalInformation.countryOfBirth` (ISO 3166-1 alpha-3) | EEA+ residents only | ✓ | ✓ |
| `nationality` | Provide `personalInformation.nationality` (ISO 3166-1 alpha-3) | EEA+ residents only | ✓ | ✓ |
| `sex` | Provide `personalInformation.gender` | — | ✓ | ✓ |
| `proof_of_address_document` | Upload via `POST /v2/document` with `documentType: "proof_of_address_document"` | — | ✓ | ✓ |
| `w8_ben_document` | Upload via `POST /v2/document` with `documentType: "w8_ben_document"` | — | ✓ | ✓ |
| `state_of_birth` | Provide `personalInformation.stateOfBirth` | — | — | ✓ |
| `expected_monthly_transaction_count`, `transaction_frequency`, `expected_counterparty_types`, `expected_geographies` | Provide `personalExpectedActivity` | — | — | ✓ |
| `ip_address`, `ip_address_approved` | Provide `ipAddress` (the customer's sign-up IP). `ip_address_approved` is resolved by SpherePay from the address's geolocation. | — | — | ✓ |
| `terms_of_service` | Accept TOS via the hosted link from `POST /v2/customer/{id}/tos-link` — only when TOS is enabled for your application | ✓ | ✓ | ✓ |
| `liveness_check`, `email_verification`, `phone_verification` | Enhanced checks via the face-verification link and OTP endpoints — only required for some integrations | ✓ | — | — |
| Item | Action required | A | C | D |
| - | - | - | - | - |
| `email_address`, `phone_number` | Provide `email` and `phone` | ✓ | ✓ | ✓ |
| `operating_address`, `registered_address` | Provide both entries in the `addresses` array | ✓ | ✓ | ✓ |
| `legal_name`, `trade_name`, `entity_type`, `description`, `website`, `incorporated_on`, `naics_code` | Provide the matching `businessInformation` fields | ✓ | ✓ | ✓ |
| `identification_number` | Provide `businessInformation.identificationNumber` and `identificationNumberType` | ✓ | ✓ | ✓ |
| `estimated_annual_revenue`, `expected_monthly_payments`, `account_purpose` | Provide `estimatedAnnualRevenueInUsd`, `expectedMonthlyPaymentsInUsd`, `accountPurpose` | ✓ | ✓ | ✓ |
| `source_of_funds`, `source_of_funds_description` | Provide `primarySourceOfFunds` and `primarySourceOfFundsDescription` | ✓ | ✓ | ✓ |
| `is_dao`, `regulated_activities`, `participates_in_regulated_financial_activity`, `operates_in_prohibited_countries` | Provide the matching `businessInformation` fields | ✓ | ✓ | ✓ |
| `business_representatives`, `control_person_added` | Register associated persons via `POST /v2/business-representative`. For EEA+ businesses this includes directors, signers, control persons and ≥ 25% owners; otherwise 25%+ owners and a control person | ✓ | ✓ | ✓ |
| `eea_control_person_present` | Register at least one representative with `isControlPerson: true` | EEA+ businesses only | — | — |
| `eea_ap_kyc_scope` | At least one director on file, and every director, signer, control person and ≥ 25% owner completes full identity verification | EEA+ businesses only | — | — |
| `incorporation_cert_document`, `shareholder_registry_document`, `proof_of_address_document` | Upload via `POST /v2/document` with the matching `documentType` | ✓ | ✓ | ✓ |
| `registration_number` | Provide `businessInformation.registrationNumber` and `registrationNumberType` | EEA+ businesses only | ✓ | ✓ |
| `incorporation_articles_document`, `w8_ben_e_document`, `bank_statement_document`, `source_of_funds_document`, `corporate_resolution_document`, `financial_statements_document` | Upload via `POST /v2/document` with the matching `documentType` | — | ✓ | ✓ |
| `expected_monthly_transaction_count`, `transaction_frequency`, `expected_counterparty_types`, `expected_geographies` | Provide `businessExpectedActivity` | — | — | ✓ |
| `ip_address`, `ip_address_approved` | Provide `ipAddress` | — | — | ✓ |
| `terms_of_service` | Accept TOS via hosted link — only when TOS is enabled for your application | ✓ | ✓ | ✓ |
Each representative has its own `ubo_kyc_profile_*` entry, returned on `GET /v2/business-representative/{id}`. Documents for a representative are uploaded with `target="business-representative"`.
| Item | Action required | A | C | D |
| - | - | - | - | - |
| `email_address`, `phone_number` | Provide `email` and `phone` | ✓ | ✓ | ✓ |
| `residential_address` | Provide the full `address` object | ✓ | ✓ | ✓ |
| `tax_identification_number` | Provide `personalInformation.taxIdentificationNumber` and its type and country | ✓ | ✓ | ✓ |
| `ownership_percentage`, `is_control_person`, `is_signer`, `relationship_established_at`, `title` | Provide `representationDetails` | ✓ | ✓ | ✓ |
| `identity_document`, `liveness_report_document`, `proof_of_address_document` | Upload via `POST /v2/document` with `target="business-representative"` | ✓ | ✓ | ✓ |
| `country_of_birth`, `nationality` | Provide `personalInformation.countryOfBirth` and `nationality` (ISO 3166-1 alpha-3) | EEA+ businesses only | ✓ | ✓ |
| `middle_name`, `sex` | Provide `middleName` and `personalInformation.gender` — validated when the representative is created | — | ✓ | ✓ |
| `occupation_soc_code`, `source_of_funds` | Provide `personalInformation.occupationSocCode` and `sourceOfFunds` | — | ✓ | ✓ |
| `w8_ben_document`, `source_of_funds_document` | Upload via `POST /v2/document` with `target="business-representative"` | — | ✓ | ✓ |
| `state_of_birth` | Provide `personalInformation.stateOfBirth` | — | — | ✓ |
See [EEA+ businesses](/concepts/onboarding/business-kyb#eea-businesses) for the full associated-person requirements.
## Handling rejected customers
A `rejected` status means SpherePay could not approve the customer for that profile. The customer cannot transact on the products that profile unlocks; other approved profiles on the same customer are unaffected.
If a customer is incorrectly rejected or requires re-review, contact [support@spherepay.co](mailto:support@spherepay.co) with the `customerId` and the profile name.
***
## Related guides
How `enabledVerificationProfiles` works and what the API returns.
Choose profiles at creation and add one to an existing customer.
Drop a profile a customer no longer needs.
Step-by-step guide for onboarding individual customers via API.
# Selecting Verification Profiles
Source: https://docs.spherepay.co/concepts/onboarding/verification-profiles/eligibility
Use enabledVerificationProfiles to choose which verification profiles a customer is evaluated against, and understand what the API accepts and returns.
Your application is enabled for a set of verification profiles by SpherePay. `enabledVerificationProfiles` lets you choose, per customer, which of those profiles the customer is evaluated against. A customer is only verified for the profiles you select, and only incurs the verification cost for those.
## The field
`enabledVerificationProfiles` is an array of profile letters on `POST /v2/customer` and `PATCH /v2/customer/{id}`. It applies to customers onboarded through the API; customers onboarded through [hosted KYC links](/concepts/onboarding/kyc-via-link) are enrolled in your application's default profiles.
```json theme={"dark"}
{
"type": "individual",
"email": "jane.smith@example.com",
"enabledVerificationProfiles": ["a", "c"]
}
```
| You send | Result |
| - | - |
| `["a"]` | The customer is evaluated against profile A only. |
| `["a", "c"]` | The customer is evaluated against A and C. Shared requirements are satisfied once for both. |
| Field omitted | The customer is enrolled in every profile your application is enabled for **at the time of creation**. Profiles SpherePay enables for you later are not added. |
| `["d"]` without `c` | Rejected — profile D requires profile C on the same customer. Send `["c", "d"]`, or add `d` to a customer that already has `c`. |
| `[]` | Rejected — a customer must have at least one profile. |
| A letter not enabled for your application | Rejected with `customer/verification-profile-family-not-enabled-for-application`. |
| A letter repeated | Rejected as a validation error. |
SpherePay recommends always sending an explicit array. It makes the customer's enrolment visible in your own records and independent of later changes to your application's configuration.
The set of profiles your application can use is configured by SpherePay. To request an additional profile for your application, contact [support@spherepay.co](mailto:support@spherepay.co).
## What the API returns
`GET /v2/customer/{id}` returns two related fields:
* `enabledVerificationProfiles` — the profiles the customer is enrolled in, always as an explicit array. If you omitted the field at creation, this is the set your application was enabled for at that moment.
* `verificationProfiles` — the profiles actually being evaluated, each with a `status` and `criteria`. Selecting a profile does not guarantee it appears here: a profile the customer is not eligible for is left out (see below).
## Country eligibility
Eligibility follows the customer's country of residence (`address.country`). **US residents can use profile A only.** **Non-US residents can use profile A, C and D.** A selected profile the customer is not eligible for is left out of `verificationProfiles`; if none of the selected profiles applies, the request is rejected.
| Customer residence | Profile A | Profile C | Profile D |
| - | - | - | - |
| United States | ✓ | — | — |
| Outside the United States | ✓ | ✓ | ✓ |
* A US resident created with `["a", "c"]` is accepted, but only `kyc_profile_a` appears in `verificationProfiles`.
* A US resident created with `["c"]` or `["d"]` is rejected with 422 `customer/verification-profile-not-enabled`, because no selected profile applies.
## Changing the selection later
Send the full new array in `PATCH /v2/customer/{id}`. Omitting the field leaves the selection unchanged.
* **Adding a profile** exposes its outstanding requirements in `criteria.required` on the new profile. Requirements already satisfied for another profile are carried over as `complete`. See [Add a profile later](/concepts/onboarding/verification-profiles/onboard-new-customer#add-a-profile-later).
* **Removing a profile** is allowed only while that profile is `incomplete`. See [Remove a profile](/concepts/onboarding/verification-profiles/remove-profile).
Adding a profile never re-reviews the customer's existing profiles. Fields those profiles already evaluated stay locked while they are `pending` or `approved` — see [When customer data can be updated](/concepts/onboarding/verification-profiles/updating-customer-data).
## Business customers
Set `enabledVerificationProfiles` on the business when you create it. Every business representative you add is evaluated against the same profiles, as `ubo_kyc_profile_*`; representatives do not carry their own selection.
Changing a business's selection after creation is not yet supported through `PATCH`. Contact your SpherePay representative if an existing business needs a new profile.
## Error reference
| Code | HTTP | Meaning |
| - | - | - |
| `customer/verification-profile-family-not-enabled-for-application` | 422 | The array names a profile your application is not enabled for. |
| `customer/verification-profile-not-enabled` | 422 | None of the selected profiles applies to this customer, for example a US resident selecting only C or D. |
| `validation/failed` | 422 | The array is empty, contains duplicates, contains an unknown letter, or includes `d` without `c`. |
| `customer/field-locked-by-verification-profile` | 400 | A field, document or profile removal is locked by a profile that is `pending` or `approved`. The body lists each locked item. See [When customer data can be updated](/concepts/onboarding/verification-profiles/updating-customer-data). |
| `customer/operation-not-allowed` | 400 | Business customers only: the selection cannot be changed after creation yet. |
***
## Related guides
Which profiles exist, what each requires and unlocks.
Choose profiles at creation and add one later.
# Onboard a Customer with Verification Profiles
Source: https://docs.spherepay.co/concepts/onboarding/verification-profiles/onboard-new-customer
Create a customer with the verification profiles they need, supply each profile's requirements, and add a profile to an existing customer later.
This guide walks through onboarding an individual customer to two verification profiles at once, then adding a profile to a customer who was created with one. For the full list of requirements per profile, see the [overview](/concepts/onboarding/verification-profile#requirements-by-profile).
## Onboard a new customer
The example onboards a Mexican individual to profiles A and C.
Send every field you already have. Fields required by either profile count once for both.
```bash theme={"dark"}
curl -X POST https://api.spherepay.co/v2/customer \
-H "Authorization: Bearer $SPHERE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "individual",
"firstName": "Ana",
"lastName": "García",
"email": "ana.garcia@example.com",
"phone": "+525551234567",
"dateOfBirth": "1990-05-15",
"address": {
"line1": "Avenida Reforma 222",
"city": "Ciudad de Mexico",
"state": "CMX",
"postalCode": "06600",
"country": "MEX"
},
"enabledVerificationProfiles": ["a", "c"],
"personalInformation": {
"nationality": "MEX",
"countryOfBirth": "MEX",
"residencyCountry": "MEX",
"taxIdentificationNumber": "GAAA900515AB1",
"taxIdentificationNumberType": "rfc",
"taxIdentificationNumberCountry": "MEX",
"gender": "female",
"occupationSocCode": "151252",
"sourceOfFunds": "salary",
"accountPurpose": "receive_salary",
"expectedMonthlyPayments": "5000_9999",
"employmentStatus": "employed",
"actingAsIntermediary": false
}
}'
```
The response lists both profiles as `incomplete`. Only documents remain.
```json theme={"dark"}
{
"id": "customer_244a09f15e254288886e419071c92da6",
"enabledVerificationProfiles": ["a", "c"],
"verificationProfiles": [
{
"name": "kyc_profile_a",
"status": "incomplete",
"criteria": {
"required": ["identity_document", "liveness_report_document", "source_of_funds_document"]
}
},
{
"name": "kyc_profile_c",
"status": "incomplete",
"criteria": {
"required": ["identity_document", "proof_of_address_document", "w8_ben_document", "source_of_funds_document", "liveness_report_document"]
}
}
]
}
```
Upload each document type once. A document that appears in `required` on both profiles satisfies both.
```bash theme={"dark"}
curl -X POST https://api.spherepay.co/v2/document \
-H "Authorization: Bearer $SPHERE_API_KEY" \
--form 'target="customer"' \
--form 'targetId="customer_244a09f15e254288886e419071c92da6"' \
--form 'documentType="id_card"' \
--form 'country="MEX"' \
--form 'side="front"' \
--form 'file=@id-front.jpg'
```
Repeat for the back of the ID card and for `proof_of_address_document`, `w8_ben_document`, `source_of_funds_document` and `liveness_report_document`.
Once every enabled profile has an empty `criteria.required`, SpherePay submits the customer for review and both profiles move to `pending`. No submit call is needed. Each profile is decided independently, so poll `GET /v2/customer/{id}` and act per profile. Or subscribe to [customer webhook events](/concepts/webhooks/event-catalog#customer-events): one event fires per profile per status change, with the profile name in `data.verificationProfile`.
```json theme={"dark"}
{
"verificationProfiles": [
{ "name": "kyc_profile_a", "status": "pending" },
{ "name": "kyc_profile_c", "status": "approved" }
]
}
```
The customer can transact on the products profile C unlocks as soon as `kyc_profile_c` is `approved`, even while `kyc_profile_a` is still `pending`.
Submission waits for **every** enabled profile. If you select A and C but only supply A's requirements, neither profile leaves `incomplete`. Supply the requirements for all selected profiles, or select fewer profiles.
## Add a profile later
When a customer created with one profile needs another — for example SpherePay enables profile C for your application and the customer wants to send USD abroad — send the full new array.
```bash theme={"dark"}
curl -X PATCH https://api.spherepay.co/v2/customer/customer_244a09f15e254288886e419071c92da6 \
-H "Authorization: Bearer $SPHERE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "enabledVerificationProfiles": ["a", "c"] }'
```
The new profile appears in `verificationProfiles`. Fields already supplied for A carry over as `complete`; documents you already uploaded are re-checked for C and listed in `criteria.pending` until that finishes. Only the C-specific requirements are `required`: `sex`, `country_of_birth`, `nationality`, `proof_of_address_document` and `w8_ben_document` for a customer who completed A.
```bash theme={"dark"}
curl -X PATCH https://api.spherepay.co/v2/customer/customer_244a09f15e254288886e419071c92da6 \
-H "Authorization: Bearer $SPHERE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"personalInformation": {
"gender": "female",
"countryOfBirth": "MEX",
"nationality": "MEX"
}
}'
```
Then upload `proof_of_address_document` and `w8_ben_document` as in the previous section.
`kyc_profile_c` moves to `pending` on its own and is decided independently. Profile A is not re-reviewed.
This works whether profile A is `incomplete`, `pending` or `approved`. Fields A already evaluated stay locked while A is `pending` or `approved`; a `PATCH` that touches one returns 400 `customer/field-locked-by-verification-profile`. See [When customer data can be updated](/concepts/onboarding/verification-profiles/updating-customer-data).
***
## Related guides
What `enabledVerificationProfiles` accepts and returns.
Drop a profile the customer no longer needs.
# Remove a Verification Profile
Source: https://docs.spherepay.co/concepts/onboarding/verification-profiles/remove-profile
Drop a verification profile a customer no longer needs. Removal is only possible while that profile is incomplete.
You can remove a profile from a customer's selection by sending the array without it. Removal is allowed only while the profile being removed is `incomplete`. Once a profile is `pending` or `approved`, it stays on the customer.
## Remove an incomplete profile
The customer below was created with `["a", "c"]` but never supplied profile C's requirements.
```bash theme={"dark"}
curl -X PATCH https://api.spherepay.co/v2/customer/customer_244a09f15e254288886e419071c92da6 \
-H "Authorization: Bearer $SPHERE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "enabledVerificationProfiles": ["a"] }'
```
The response no longer lists `kyc_profile_c`, and profile A is unaffected.
```json theme={"dark"}
{
"id": "customer_244a09f15e254288886e419071c92da6",
"enabledVerificationProfiles": ["a"],
"verificationProfiles": [
{ "name": "kyc_profile_a", "status": "incomplete" }
]
}
```
Removing a profile that still has outstanding requirements is the quickest way to unblock a customer. Submission for review waits for every enabled profile to be complete, so a profile you do not intend to finish holds back the ones you do.
## Profiles that cannot be removed
A profile that is `pending` or `approved` cannot be removed. The request is rejected with 400 `customer/field-locked-by-verification-profile` and the customer is unchanged.
```json theme={"dark"}
{
"status": 400,
"code": "customer/field-locked-by-verification-profile",
"detail": "One or more fields are locked by a verification profile that is pending or approved.",
"errors": [
{
"detail": "Verification profile family \"c\" cannot be removed while profile kyc_profile_c is pending.",
"pointer": "/enabledVerificationProfiles"
}
]
}
```
A customer must keep at least one profile: an empty array is rejected as a validation error.
***
## Related guides
What `enabledVerificationProfiles` accepts and returns.
Choose profiles at creation and add one later.
# When Customer Data Can Be Updated
Source: https://docs.spherepay.co/concepts/onboarding/verification-profiles/updating-customer-data
Which customer fields and documents can be changed with PATCH and document uploads, based on the status of each verification profile and the profiles that already evaluated them.
Whether a field or document can be changed depends on two things: the **status** of each of the customer's verification profiles, and **which profiles already evaluated** that field. This page gives the rule, then applies it to individual customers.
## The rule
A field or document is **locked** when it is a `complete` criterion of a verification profile whose status is `pending` or `approved`. Everything else can be changed with `PATCH /v2/customer/{id}` or `POST /v2/document`.
| Profile status | Effect on the criteria it evaluated |
| - | - |
| `incomplete` | Nothing locked. Supply or correct any field or document. |
| `pending` | All `complete` criteria locked while the review is in progress. |
| `approved` | All `complete` criteria locked. |
| `rejected`, `resubmission_required` | Nothing locked. Correct what `criteria.errors` lists and the profile is resubmitted on its own. |
Criteria shared by several profiles are locked if **any** of those profiles is `pending` or `approved`. Criteria evaluated by none of the customer's locking profiles stay editable, which is what lets you [add a profile to a pending or approved customer](/concepts/onboarding/verification-profiles/onboard-new-customer#add-a-profile-later). Documents you already uploaded for an earlier profile are re-checked for a newly added profile and appear in its `criteria.pending` until that check completes; data fields carry over as `complete` immediately.
A rejected `PATCH` returns 400 `customer/field-locked-by-verification-profile` and applies nothing. The body names each locked field and the profile locking it:
```json theme={"dark"}
{
"status": 400,
"code": "customer/field-locked-by-verification-profile",
"detail": "One or more fields are locked by a verification profile that is pending or approved.",
"errors": [
{
"detail": "personalInformation.actingAsIntermediary is locked by verification profile kyc_profile_a (pending).",
"pointer": "/personalInformation/actingAsIntermediary"
}
]
}
```
Document uploads return the same code; the error entry carries `parameter: "documentType"` and names the locking profile in `detail`. Removing a profile from `enabledVerificationProfiles` while it is `pending` or `approved` returns the same code with `pointer: "/enabledVerificationProfiles"`.
## Always immutable
`firstName`, `lastName`, `dateOfBirth`, `email` and `phone` cannot be changed after creation in any status. Create a new customer if these are wrong.
## Individual customers
Locks follow the customer's `kyc_profile_*` entries. The table shows which profiles evaluate each criterion; a criterion is locked while any ticked profile is `pending` or `approved`.
| Criterion | Field or document | A | C | D |
| - | - | - | - | - |
| `residential_address` | `address` | ✓ | ✓ | ✓ |
| `tax_identification_number` | `personalInformation.taxIdentificationNumber*` | ✓ | ✓ | ✓ |
| `identity_document`, `liveness_report_document`, `source_of_funds_document` | document uploads | ✓ | ✓ | ✓ |
| `source_of_funds`, `occupation_soc_code` | `personalInformation.sourceOfFunds`, `occupationSocCode` | ✓ | ✓ | ✓ |
| `account_purpose`, `employment_status`, `acting_as_intermediary` | `personalInformation.accountPurpose`, `employmentStatus`, `actingAsIntermediary` | ✓ | — | — |
| `expected_monthly_payments` | `personalInformation.expectedMonthlyPayments` | ✓ | — | ✓ |
| `country_of_birth`, `nationality` | `personalInformation.countryOfBirth`, `nationality` | EEA+ residents | ✓ | ✓ |
| `sex` | `personalInformation.gender` | — | ✓ | ✓ |
| `proof_of_address_document`, `w8_ben_document` | document uploads | — | ✓ | ✓ |
| `state_of_birth` | `personalInformation.stateOfBirth` | — | — | ✓ |
| expected activity criteria | `personalExpectedActivity` | — | — | ✓ |
| `ip_address` | `ipAddress` | — | — | ✓ |
**Example — profile A pending or approved, adding profile C.** Everything in column A is locked. `gender`, `countryOfBirth` and `nationality` (for a non-EEA+ customer), plus the proof of address and W-8BEN uploads, are open because only C evaluates them. Once they are in, `kyc_profile_c` goes `pending` and locks them too.
**Example — profile A rejected.** Nothing is locked. Fix the fields listed in `criteria.errors`, re-upload documents if asked, and the profile is resubmitted automatically.
## Business customers
`PATCH /v2/customer/{id}` is not yet available for business customers, including changes to `enabledVerificationProfiles`; it returns 400 `customer/operation-not-allowed`. Document uploads for the business and its representatives are unaffected. Contact [support@spherepay.co](mailto:support@spherepay.co) if an existing business needs a new profile.
***
## Related guides
Statuses, criteria and requirements per profile.
Choose profiles at creation and add one later.
# API Rate Limits and Best Practices
Source: https://docs.spherepay.co/concepts/operational/rate-limits
SpherePay enforces 100 RPS for read and write operations. Learn how to handle rate limit errors, implement retry logic, and request temporary increases.
SpherePay enforces rate limits on all API requests to ensure reliability and fair performance for all users. Understanding these limits and designing your integration around them will help you avoid disruptions as you scale.
## Standard rate limits
SpherePay measures rate limits in requests per second (RPS) per application.
| Operation | HTTP methods | Rate limit |
| - | - | - |
| Read | GET | 100 RPS |
| Write | POST, PUT, PATCH, DELETE | 100 RPS |
If you have arranged custom limits with the SpherePay support team, your rate limits may differ from the defaults above.
## Requesting a rate limit increase
If your use case requires higher limits — for example, during a product launch or a high-traffic event — contact [support@spherepay.co](mailto:support@spherepay.co) to request a temporary or permanent increase.
## Load testing
If you need to conduct load testing against SpherePay APIs, simulate requests using realistic latency parameters rather than maximum throughput. For complex integrations or dedicated load testing windows, reach out to [support@spherepay.co](mailto:support@spherepay.co) before running tests.
SpherePay can provide designated load testing windows for integrations that require it. Contact the support team to arrange this in advance.
## Best practices
Following these guidelines will help you stay within rate limits and maintain a reliable integration:
* **Monitor API usage** — track your request rates regularly so you can identify spikes before they cause errors.
* **Implement exponential backoff** — when you receive a rate limit error, wait and retry with increasing delays. See the [AWS Retry with Backoff Pattern guide](https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/retry-backoff.html) for a reference implementation.
* **Cache efficiently** — store frequently read data locally to reduce repeated GET requests for the same resources.
* **Optimize requests** — batch operations where possible and request only the data your application actually needs.
# SpherePay Concepts and Reference Data Overview
Source: https://docs.spherepay.co/concepts/overview
Core mental models and reference data for building on SpherePay — onboarding, transfers, automation, supported rails, and operational details.
The Concepts section explains how SpherePay's building blocks fit together. Read these pages before integrating — they cover the platform's two onboarding models, the transfer lifecycle, the automation primitives, and the reference data (countries, assets, codes) you'll wire up against.
Not sure which build path to follow after discovery? Read [Implementation Guides](/implementation-guides/overview) to route to the right implementation pattern before choosing a [Solutions](/solutions/overview) guide.
## Where to go next
KYC and KYB flows, Sphere-Managed vs. Platform-Managed verification, and how to use the hosted KYC link.
Move money on and off the chain — the transfer lifecycle, statuses, and how bank accounts and wallets attach to a customer.
Onramper Accounts and Offloader Wallets — the two primitives that turn manual conversions into hands-off flows.
Supported countries, assets and networks, and channel codes you'll reference in API requests.
Rate limits, retry behavior, and the operational guardrails you should know before going live.
# Payment Channel Codes in SpherePay
Source: https://docs.spherepay.co/concepts/reference/channel-codes
Reference for network and channel codes used in SpherePay transfer requests, including ach, wire, sepa, pix, and blockchain network identifiers.
Channel codes identify the payment rail or blockchain network used to send or receive funds in a SpherePay transfer. You specify them in the `network` field of `source` and `destination` objects in transfer requests, virtual account configurations, and offloader wallet setups.
## Fiat payment channels
Use these codes when specifying a bank account as the source or destination of a transfer.
| Channel | Code | Description |
| - | - | - |
| ACH | `ach` | US ACH bank transfer. Sent via same-day ACH by default. |
| Wire | `wire` | USD wire transfer. Suitable for large or international transfers. Settles in 0–3 business days. |
| SEPA | `sepa` | Euro bank transfer in the European Union. Settles in 0–1 business days. |
| PIX | `pix` | Brazilian instant payment network for BRL transfers. |
`achSameDay` is no longer a supported channel code. All ACH transfers are sent via same-day ACH by default — use `ach`.
## Blockchain network codes
Use these codes when specifying a wallet as the source or destination of a transfer.
| Network | Code | Notes |
| - | - | - |
| Ethereum | `ethereum` | Supports USDC, USDT, EURC |
| Solana | `sol` | Supports USDC, EURC |
| Tron | `tron` | Supports USDT |
| Polygon | `polygon` | Supports USDC |
| Base | `base` | Supports USDC, EURC |
| Arbitrum | `arbitrum` | Supports USDC |
| Avalanche | `avalanche` | Supports USDC |
| Starknet | `starknet` | Supports USDC |
Use fiat channel codes (such as `ach` or `sepa`) for bank account sources and destinations. Use blockchain network codes (such as `ethereum` or `sol`) for wallet sources and destinations. Mixing the two in the same object will result in an error.
# Prohibited Countries and Sanctioned Regions
Source: https://docs.spherepay.co/concepts/reference/prohibited-countries
Reference list of countries and jurisdictions where SpherePay accounts cannot be opened, plus the regional restrictions that apply for sanctioned regions.
You can open a Sphere account as an individual or business in most countries of the world. This page lists the exceptions — countries where Sphere accounts are unavailable, and regions with partial restrictions.
Applying as a resident of a country not listed below is a requirement, but does **not** guarantee account approval. All applicants must pass [KYC](/concepts/onboarding/individual-kyc) or [KYB](/concepts/onboarding/business-kyb) verification; some countries trigger additional checks.
## Prohibited countries (individuals and businesses)
Sphere accounts cannot be opened from these countries. This list includes [OFAC-sanctioned](https://ofac.treasury.gov/sanctions-programs-and-country-information) jurisdictions and others where Sphere does not operate:
* Afghanistan
* Algeria
* Bangladesh
* Belarus
* Burundi
* Central African Republic
* Chad
* China
* Cuba
* Democratic People's Republic of Korea
* Democratic Republic of the Congo
* Eritrea
* Ethiopia
* Haiti
* Iran
* Iraq
* Kazakhstan
* Kenya
* Kosovo
* Lebanon
* Libya
* Macedonia
* Mali
* Morocco
* Myanmar
* Nepal
* Nicaragua
* Niger
* Pakistan
* Palestine
* Qatar
* Russian Federation
* Sint Maarten
* Slovenia
* Somalia
* South Sudan
* Sudan
* Syria
* Ukraine (including Crimea)
* Venezuela
* Yemen
* Zimbabwe
## Restricted for individuals only
Sphere is available in these countries for **business accounts only**. Individual residents cannot currently open a personal Sphere account:
* Finland
* Hungary
* Latvia
* Lithuania
* Netherlands
* Slovenia
## U.S. state restrictions
### New York
Sphere can only serve a limited range of products and services to New York residents. If you're applying from or operating in New York, **contact Business Development before integrating** to confirm product eligibility for your use case.
## Additional verification
Some countries not listed above may still trigger enhanced due diligence based on the applicant's profile, transaction patterns, or product mix. If you have questions about a specific corridor or customer base, reach out to [support@spherepay.co](mailto:support@spherepay.co).
## Source and updates
This list mirrors Sphere's published [country availability policy](https://intercom.help/spherepay/en/articles/9516473-which-countries-can-i-use-sphere-from). The canonical source is Sphere's compliance team — if you see a discrepancy between this page and the Help Center article, the Help Center is authoritative.
**Last updated:** December 2, 2025
# Supported Currency Codes in SpherePay
Source: https://docs.spherepay.co/concepts/reference/supported-assets
Reference for all fiat and stablecoin currency codes used in SpherePay API requests. Includes USD, EUR, BRL, USDC, USDT, and EURC with supported networks.
SpherePay uses lowercase currency codes in API requests and responses. Use the codes in the table below when specifying currencies in transfer requests, virtual accounts, offloader wallets, and bank account fields.
Always use lowercase currency codes in API requests (for example, `"currency": "usdc"`, not `"USDC"`).
## Fiat currencies
| Currency name | Code (API) | Type | Supported rails |
| - | - | - | - |
| United States Dollar | `usd` | Fiat | ACH, ACH Same Day, Wire |
| Euro | `eur` | Fiat | SEPA |
| Brazilian Real | `brl` | Fiat | PIX |
## Stablecoins
| Currency name | Code (API) | Type | Supported networks |
| - | - | - | - |
| USD Coin | `usdc` | Stablecoin | Arbitrum, Avalanche, Base, Ethereum, Polygon, Solana, Starknet |
| Tether | `usdt` | Stablecoin | Ethereum, Tron |
| Euro Coin | `eurc` | Stablecoin | Base, Ethereum, Solana |
## Notes
* BRL transfers via PIX require a separate verification profile (`kyc_profile_b` / `kyb_profile_b`). Contact your SpherePay representative for access.
* Not all currency and network combinations are available for every product. Check the supported rails documentation for [Transfers](/concepts/transfers/overview), [Onramper Accounts](/concepts/automation/onramper-accounts), and [Offloader Wallets](/concepts/automation/offloader-wallets).
# Country Codes Reference for SpherePay API
Source: https://docs.spherepay.co/concepts/reference/supported-countries
SpherePay uses ISO 3166-1 alpha-3 country codes in customer address fields. Reference common country codes used when creating customers and bank accounts.
SpherePay uses ISO 3166-1 alpha-3 (three-letter) country codes in customer and bank account address fields. You'll encounter these codes in the `address.country` field when creating customers and registering bank accounts.
Not all countries are available on SpherePay. Regions subject to sanctions or other legal restrictions are not supported. Contact SpherePay if you are unsure whether a specific country is available.
## Where country codes appear
* `address.country` when creating or updating a customer
* `address.country` when registering a bank account
* Identity document fields during KYC/KYB onboarding
## Common country codes
The table below lists frequently used country codes. For the complete list of ISO 3166-1 alpha-3 codes, see the [full reference table](#full-reference-table) below.
| Country | ISO Alpha-2 | ISO Alpha-3 |
| - | - | - |
| Australia | AU | AUS |
| Brazil | BR | BRA |
| Canada | CA | CAN |
| China | CN | CHN |
| France | FR | FRA |
| Germany | DE | DEU |
| India | IN | IND |
| Japan | JP | JPN |
| Mexico | MX | MEX |
| Singapore | SG | SGP |
| United Kingdom | GB | GBR |
| United States | US | USA |
## Full reference table
| Country | Dialing code | ISO Alpha-2 | ISO Alpha-3 |
| - | - | - | - |
| Afghanistan | 93 | AF | AFG |
| Aland Islands | 358 18 | AX | ALA |
| Albania | 355 | AL | ALB |
| Algeria | 213 | DZ | DZA |
| American Samoa | 1 | AS | ASM |
| Andorra | 376 | AD | AND |
| Angola | 244 | AO | AGO |
| Anguilla | 1 | AI | AIA |
| Antigua and Barbuda | 1 | AG | ATG |
| Argentina | 54 | AR | ARG |
| Armenia | 374 | AM | ARM |
| Aruba | 297 | AW | ABW |
| Australia | 61 | AU | AUS |
| Austria | 43 | AT | AUT |
| Azerbaijan | 994 | AZ | AZE |
| Bahamas, The | 1 | BS | BHS |
| Bahrain | 973 | BH | BHR |
| Bangladesh | 880 | BD | BGD |
| Barbados | 1 | BB | BRB |
| Belarus | 375 | BY | BLR |
| Belgium | 32 | BE | BEL |
| Belize | 501 | BZ | BLZ |
| Benin | 229 | BJ | BEN |
| Bermuda | 1 | BM | BMU |
| Bhutan | 975 | BT | BTN |
| Bolivia | 591 | BO | BOL |
| Bosnia and Herzegovina | 387 | BA | BIH |
| Botswana | 267 | BW | BWA |
| Brazil | 55 | BR | BRA |
| British Virgin Islands | 1 | VG | VGB |
| Brunei | 673 | BN | BRN |
| Bulgaria | 359 | BG | BGR |
| Burkina Faso | 226 | BF | BFA |
| Burundi | 257 | BI | BDI |
| Cambodia | 855 | KH | KHM |
| Cameroon | 237 | CM | CMR |
| Canada | 1 | CA | CAN |
| Cape Verde | 238 | CV | CPV |
| Cayman Islands | 1 | KY | CYM |
| Central African Republic | 236 | CF | CAF |
| Chad | 235 | TD | TCD |
| Chile | 56 | CL | CHL |
| China, People's Republic of | 86 | CN | CHN |
| China, Republic of (Taiwan) | 886 | TW | TWN |
| Colombia | 57 | CO | COL |
| Comoros | 269 | KM | COM |
| Congo (Congo-Brazzaville) | 242 | CG | COG |
| Congo (Congo-Kinshasa) | 243 | CD | COD |
| Cook Islands | 682 | CK | COK |
| Costa Rica | 506 | CR | CRI |
| Côte d'Ivoire (Ivory Coast) | 225 | CI | CIV |
| Croatia | 385 | HR | HRV |
| Cuba | 53 | CU | CUB |
| Cyprus | 357 | CY | CYP |
| Czech Republic | 420 | CZ | CZE |
| Denmark | 45 | DK | DNK |
| Djibouti | 253 | DJ | DJI |
| Dominica | 1 | DM | DMA |
| Dominican Republic | 1 | DO | DOM |
| Ecuador | 593 | EC | ECU |
| Egypt | 20 | EG | EGY |
| El Salvador | 503 | SV | SLV |
| Equatorial Guinea | 240 | GQ | GNQ |
| Eritrea | 291 | ER | ERI |
| Estonia | 372 | EE | EST |
| Ethiopia | 251 | ET | ETH |
| Falkland Islands | 500 | FK | FLK |
| Faroe Islands | 298 | FO | FRO |
| Fiji | 679 | FJ | FJI |
| Finland | 358 | FI | FIN |
| France | 33 | FR | FRA |
| Gabon | 241 | GA | GAB |
| Gambia | 220 | GM | GMB |
| Georgia | 995 | GE | GEO |
| Germany | 49 | DE | DEU |
| Ghana | 233 | GH | GHA |
| Gibraltar | 350 | GI | GIB |
| Greece | 30 | GR | GRC |
| Greenland | 299 | GL | GRL |
| Grenada | 1 | GD | GRD |
| Guam | 1 | GU | GUM |
| Guatemala | 502 | GT | GTM |
| Guinea | 224 | GN | GIN |
| Guinea-Bissau | 245 | GW | GNB |
| Guyana | 592 | GY | GUY |
| Haiti | 509 | HT | HTI |
| Honduras | 504 | HN | HND |
| Hong Kong | 852 | HK | HKG |
| Hungary | 36 | HU | HUN |
| Iceland | 354 | IS | ISL |
| India | 91 | IN | IND |
| Indonesia | 62 | ID | IDN |
| Iran | 98 | IR | IRN |
| Iraq | 964 | IQ | IRQ |
| Ireland | 353 | IE | IRL |
| Israel | 972 | IL | ISR |
| Italy | 39 | IT | ITA |
| Jamaica | 1 | JM | JAM |
| Japan | 81 | JP | JPN |
| Jordan | 962 | JO | JOR |
| Kazakhstan | 7 | KZ | KAZ |
| Kenya | 254 | KE | KEN |
| Korea, North | 850 | KP | PRK |
| Korea, South | 82 | KR | KOR |
| Kuwait | 965 | KW | KWT |
| Kyrgyzstan | 996 | KG | KGZ |
| Laos | 856 | LA | LAO |
| Latvia | 371 | LV | LVA |
| Lebanon | 961 | LB | LBN |
| Lesotho | 266 | LS | LSO |
| Liberia | 231 | LR | LBR |
| Libya | 218 | LY | LBY |
| Liechtenstein | 423 | LI | LIE |
| Lithuania | 370 | LT | LTU |
| Luxembourg | 352 | LU | LUX |
| Macau | 853 | MO | MAC |
| Macedonia | 389 | MK | MKD |
| Madagascar | 261 | MG | MDG |
| Malawi | 265 | MW | MWI |
| Malaysia | 60 | MY | MYS |
| Maldives | 960 | MV | MDV |
| Mali | 223 | ML | MLI |
| Malta | 356 | MT | MLT |
| Marshall Islands | 692 | MH | MHL |
| Mauritania | 222 | MR | MRT |
| Mauritius | 230 | MU | MUS |
| Mexico | 52 | MX | MEX |
| Micronesia | 691 | FM | FSM |
| Moldova | 373 | MD | MDA |
| Monaco | 377 | MC | MCO |
| Mongolia | 976 | MN | MNG |
| Montenegro | 382 | ME | MNE |
| Morocco | 212 | MA | MAR |
| Mozambique | 258 | MZ | MOZ |
| Myanmar (Burma) | 95 | MM | MMR |
| Namibia | 264 | NA | NAM |
| Nepal | 977 | NP | NPL |
| Netherlands | 31 | NL | NLD |
| New Zealand | 64 | NZ | NZL |
| Nicaragua | 505 | NI | NIC |
| Niger | 227 | NE | NER |
| Nigeria | 234 | NG | NGA |
| Norway | 47 | NO | NOR |
| Oman | 968 | OM | OMN |
| Pakistan | 92 | PK | PAK |
| Palau | 680 | PW | PLW |
| Palestine | 970 | PS | PSE |
| Panama | 507 | PA | PAN |
| Papua New Guinea | 675 | PG | PNG |
| Paraguay | 595 | PY | PRY |
| Peru | 51 | PE | PER |
| Philippines | 63 | PH | PHL |
| Poland | 48 | PL | POL |
| Portugal | 351 | PT | PRT |
| Qatar | 974 | QA | QAT |
| Romania | 40 | RO | ROU |
| Russia | 7 | RU | RUS |
| Rwanda | 250 | RW | RWA |
| Saudi Arabia | 966 | SA | SAU |
| Senegal | 221 | SN | SEN |
| Serbia | 381 | RS | SRB |
| Seychelles | 248 | SC | SYC |
| Sierra Leone | 232 | SL | SLE |
| Singapore | 65 | SG | SGP |
| Slovakia | 421 | SK | SVK |
| Slovenia | 386 | SI | SVN |
| Somalia | 252 | SO | SOM |
| South Africa | 27 | ZA | ZAF |
| South Sudan | 211 | SS | SSD |
| Spain | 34 | ES | ESP |
| Sri Lanka | 94 | LK | LKA |
| Sudan | 249 | SD | SDN |
| Suriname | 597 | SR | SUR |
| Sweden | 46 | SE | SWE |
| Switzerland | 41 | CH | CHE |
| Syria | 963 | SY | SYR |
| Tajikistan | 992 | TJ | TJK |
| Tanzania | 255 | TZ | TZA |
| Thailand | 66 | TH | THA |
| Timor-Leste | 670 | TL | TLS |
| Togo | 228 | TG | TGO |
| Tonga | 676 | TO | TON |
| Trinidad and Tobago | 1 | TT | TTO |
| Tunisia | 216 | TN | TUN |
| Turkey | 90 | TR | TUR |
| Turkmenistan | 993 | TM | TKM |
| Uganda | 256 | UG | UGA |
| Ukraine | 380 | UA | UKR |
| United Arab Emirates | 971 | AE | ARE |
| United Kingdom | 44 | GB | GBR |
| United States | 1 | US | USA |
| Uruguay | 598 | UY | URY |
| Uzbekistan | 998 | UZ | UZB |
| Vanuatu | 678 | VU | VUT |
| Venezuela | 58 | VE | VEN |
| Vietnam | 84 | VN | VNM |
| Yemen | 967 | YE | YEM |
| Zambia | 260 | ZM | ZMB |
| Zimbabwe | 263 | ZW | ZWE |
# Register Bank Accounts for Fiat Transfers
Source: https://docs.spherepay.co/concepts/transfers/bank-accounts
Link a customer's fiat bank account to SpherePay to enable on-ramp and off-ramp transfers via ACH, Wire, SEPA, or PIX payment rails.
A bank account in SpherePay is a registration of your customer's existing fiat bank account. SpherePay does not open or manage bank accounts — it links an account your customer already holds to their SpherePay profile so it can be used as the source or destination for fiat transfers. You must register at least one bank account before creating any transfer that involves fiat currency.
SpherePay registers an existing bank account — it does not open a new account on your customer's behalf. The account must already exist at the customer's bank before registration.
## Register a bank account
`POST https://api.spherepay.co/v2/bank-account`
For the full request and response schema, see the [Create Bank Account API reference](/api-reference/bank-account/post).
## Supported rails and currencies
| Currency | Supported Rails |
| - | - |
| USD | ACH, Wire |
| EUR | SEPA |
| BRL | PIX |
## Required fields by currency
The fields required to register a bank account depend on the currency. Use the tabs below to see the required fields and a request example for each.
Both ACH and Wire use the same account details. Include both in `networks` to register for both rails with a single request.
| Field | Required |
| - | - |
| `customerId` | Yes |
| `bankName` | Yes |
| `accountName` | Yes |
| `accountOwner.accountHolderName` | Yes |
| `accountOwner.address` | Yes |
| `currency` | Yes — `"usd"` |
| `accountDetails.accountNumber` | Yes |
| `accountDetails.routingNumber` | Yes |
| `accountDetails.accountType` | Yes — `"checking"` or `"savings"` |
| `networks` | Yes — `["ach"]`, `["wire"]`, or `["ach", "wire"]` |
```json theme={"dark"}
{
"customerId": "customer_1234567890",
"bankName": "Chase",
"accountName": "Jane Doe Checking",
"accountOwner": {
"accountHolderName": "Jane Doe",
"address": {
"line1": "233 South Wacker Drive",
"city": "Chicago",
"state": "IL",
"postalCode": "60606",
"country": "USA"
}
},
"currency": "usd",
"accountDetails": {
"accountNumber": "1234567890",
"routingNumber": "123456789",
"accountType": "checking"
},
"networks": ["ach", "wire"]
}
```
EUR accounts use IBAN and BIC for SEPA transfers.
| Field | Required |
| - | - |
| `customerId` | Yes |
| `bankName` | Yes |
| `accountName` | Yes |
| `accountOwner.accountHolderName` | Yes |
| `accountOwner.address` | Yes |
| `currency` | Yes — `"eur"` |
| `accountDetails.iban` | Yes |
| `accountDetails.bic` | Yes |
| `networks` | Yes — `["sepa"]` |
```json theme={"dark"}
{
"customerId": "customer_1234567890",
"bankName": "Deutsche Bank",
"accountName": "Jane Doe SEPA",
"accountOwner": {
"accountHolderName": "Jane Doe",
"address": {
"line1": "10 Rue de Rivoli",
"city": "Paris",
"postalCode": "75001",
"country": "FRA"
}
},
"currency": "eur",
"accountDetails": {
"iban": "DE89370400440532013000",
"bic": "COBADEFFXXX"
},
"networks": ["sepa"]
}
```
BRL accounts use PIX, Brazil's instant payment network. Instead of an account number and routing number, you provide a PIX key and key type.
| Field | Required |
| - | - |
| `customerId` | Yes |
| `bankName` | Yes |
| `accountName` | Yes |
| `accountOwner.accountHolderName` | Yes |
| `accountOwner.address` | Yes — `postalCode` is required |
| `currency` | Yes — `"brl"` |
| `accountDetails.pixKey` | Yes |
| `accountDetails.pixKeyType` | Yes — `"cpf"`, `"cnpj"`, `"email"`, `"phone"`, or `"random"` |
| `networks` | Yes — `["pix"]` |
```json theme={"dark"}
{
"customerId": "customer_1234567890",
"bankName": "Itaú",
"accountName": "Alice Santos PIX",
"accountOwner": {
"accountHolderName": "Alice Santos",
"address": {
"line1": "Rua Augusta 100",
"city": "Sao Paulo",
"state": "SP",
"postalCode": "01304-001",
"country": "BRA"
}
},
"currency": "brl",
"accountDetails": {
"pixKey": "alice@example.com",
"pixKeyType": "email"
},
"networks": ["pix"]
}
```
BRL transfers require a separate verification profile (`kyc_profile_b` for individuals, `kyb_profile_b` for businesses). Contact your SpherePay representative to enable BRL/PIX access. See [BRL/PIX transfer constraints](/concepts/transfers/overview#brlpix-transfer-constraints) for the full list of rules.
## Using the returned ID
The `id` returned in the response is your persistent reference to this bank account. Store it against the customer record and pass it as the `source.id` or `destination.id` when creating transfers.
```json theme={"dark"}
{
"id": "bank_account_abc123",
"customerId": "customer_1234567890",
"currency": "usd",
"networks": ["ach", "wire"],
...
}
```
# Transfer Lifecycle and Status Reference
Source: https://docs.spherepay.co/concepts/transfers/lifecycle
Understand how SpherePay transfers move through each status — from pendingFunding through succeeded, returned, or failed — and how to handle each state.
A transfer in SpherePay is the movement of funds between financial accounts — a wallet and a bank account. Every transfer has a source and a destination. After you create a transfer, SpherePay moves it through a series of statuses as funds are received, processed, and delivered. Understanding these statuses lets you build accurate status displays and handle edge cases like failures and refunds.
## The `instructions` field
When you create a transfer, the response includes an `instructions` field that describes what funds the customer needs to send and where. For off-ramp transfers, the `depositInstructions` sub-field contains the details your customer needs to initiate the on-chain transaction — the destination address, network, currency, and amount.
Use `depositInstructions` to build and deliver transaction details directly to your customers for off-ramp transfers. Present the address, network, and amount clearly so they can send the exact amount to the correct address.
Transfers that are funded are executed on the next transfer execution cycle.
## Transfer statuses
The table below lists every possible status a transfer can reach, in the order they typically occur.
| Status | Description |
| - | - |
| `pendingFunding` | SpherePay is waiting to receive funds from the customer before processing the transfer. This applies to incoming crypto deposits, wires, and ACH transfers. |
| `pendingReview` | A rare, temporary state triggered when transfer data needs further confirmation. Typically resolves in seconds; otherwise SpherePay will reach out within 24 hours. |
| `fundsReceived` | SpherePay has received the funds from the customer and is preparing to process the transfer. |
| `processing` | SpherePay is actively processing the transfer. |
| `succeeded` | The transfer has succeeded and the funds have been delivered to the destination. |
| `returned` | The transfer was returned by the destination bank. A refund will be initiated back to the customer. |
| `pendingRefundInformation` | SpherePay is waiting for the customer to provide further information needed to complete the refund. |
| `failed` | The transfer has failed. A refund will be initiated back to the customer. |
| `canceled` | The transfer was canceled by the customer. A refund will be initiated back to the customer. |
| `refunded` | The refund has been delivered to the customer. |
| `unexpectedError` | The transfer encountered an unexpected error. A refund will be initiated back to the customer. |
| `failedPrecondition` | The transfer failed because the customer provided invalid information. A refund will be initiated back to the customer. |
| `undeliverable` *(deprecated)* | This status is no longer used. |
## Tracking status updates
SpherePay can push transfer status updates to your systems via [webhooks](/concepts/webhooks/overview) — subscribe to the [`transfer.*` events](/concepts/webhooks/event-catalog#transfer-events) to be notified of every transition listed above. Alternatively, poll `GET /v2/transfer/{id}` at a reasonable interval to track progress.
```bash theme={"dark"}
curl https://api.spherepay.co/v2/transfer/{{transfer_id}} \
-H "Authorization: Bearer {{api_key}}"
```
Poll until the transfer reaches a terminal state: `succeeded`, `refunded`, `failed`, `canceled`, `returned`, `unexpectedError`, or `failedPrecondition`. Once a transfer enters a terminal state, its status will not change.
See the [Get a Transfer API reference](/api-reference/transfer/get-id) for the full response schema.
# Transfers API: On-Ramp and Off-Ramp Guide
Source: https://docs.spherepay.co/concepts/transfers/overview
Create on-ramp and off-ramp transfers with the SpherePay Transfers API. Covers integration workflow, request examples for USD and BRL/PIX, and status tracking.
The Transfers API lets you create on-ramp and off-ramp transfers for your customers and track their lifecycle from creation to completion or failure. An on-ramp converts fiat currency (USD, EUR, BRL) into a stablecoin (USDC, USDT, EURC) deposited to a blockchain wallet. An off-ramp does the reverse — it converts stablecoin into fiat and delivers it to a bank account. Both directions use the same `POST /v2/transfer` endpoint; the direction is determined by what you set as `source` and `destination`.
Transfers can support multiple business models. Before implementation, confirm whether the movement is [first-party](/implementation-guides/first-party-flows) or [third-party](/implementation-guides/third-party-flows) so you can identify the correct customer of record, source, destination, and required payment data.
## When to use the Transfers API
Use the Transfers API when you want to embed transfers directly into your product and control your own UX. You initiate each transfer explicitly, and SpherePay returns unique deposit instructions per request — the customer sends funds to those instructions, and SpherePay matches the memo to process it.
If you want a pre-built, hosted experience with no frontend work, use the [Ramp Widget](/widget/overview) instead.
| | Transfers API | Ramp Widget |
| - | - | - |
| **Control** | Full — you initiate and track every transfer | Minimal — SpherePay hosts the UX |
| **UX ownership** | Your product | SpherePay-hosted widget |
| **Best for** | Custom integrations, programmatic flows | Quick embeds, prototypes |
## Integration overview
Complete KYC (individuals) or KYB (businesses) before any transfer can be created. The customer's verification profile must be `approved`. See [Customers & Onboarding](/concepts/onboarding/overview).
Register the bank account and wallet that will be used as the source and destination for the transfer.
* Bank accounts: [POST /v2/bank-account](/concepts/transfers/bank-accounts)
* Wallets: [POST /v2/wallet](/concepts/transfers/wallets)
Call `POST /v2/transfer` with the customer, amount, source, and destination. See full examples below.
Poll `GET /v2/transfer/{id}` until the transfer reaches a terminal state (`succeeded`, `failed`, `refunded`, etc.). See [Transfer Lifecycle](/concepts/transfers/lifecycle).
## Transfer creation
`POST https://api.spherepay.co/v2/transfer`
Use the tabs below to see request bodies for each supported transfer direction.
Sends USD from a bank account via ACH and delivers USDC to an Ethereum wallet.
```bash theme={"dark"}
curl -X POST https://api.spherepay.co/v2/transfer \
-H "Authorization: Bearer {{api_key}}" \
-H "Content-Type: application/json" \
-d '{
"amount": "100.00",
"customer": "{{customer_id}}",
"source": {
"type": "bank_account",
"id": "{{bank_account_id}}",
"currency": "usd",
"network": "ach"
},
"destination": {
"type": "wallet",
"id": "{{wallet_id}}",
"currency": "usdc",
"network": "ethereum"
}
}'
```
Sends USDC from an Ethereum wallet and delivers USD to a bank account via ACH.
When choosing the destination `network`, consider whether the beneficiary needs to identify the sender. Named payouts — where the sender's business name appears on the receiving bank statement — are [wire-only](/concepts/transfers/supported-rails). Beneficiary banks may reject payments that arrive without an identifiable sender name.
```bash theme={"dark"}
curl -X POST https://api.spherepay.co/v2/transfer \
-H "Authorization: Bearer {{api_key}}" \
-H "Content-Type: application/json" \
-d '{
"amount": "100.00",
"customer": "{{customer_id}}",
"source": {
"type": "wallet",
"id": "{{wallet_id}}",
"currency": "usdc",
"network": "ethereum"
},
"destination": {
"type": "bank_account",
"id": "{{bank_account_id}}",
"currency": "usd",
"network": "ach"
}
}'
```
Sends BRL from a PIX bank account and delivers USDC to a Polygon wallet.
```bash theme={"dark"}
curl -X POST https://api.spherepay.co/v2/transfer \
-H "Authorization: Bearer {{api_key}}" \
-H "Content-Type: application/json" \
-d '{
"amount": "500.00",
"customer": "{{customer_id}}",
"source": {
"type": "bank_account",
"id": "{{bank_account_id}}",
"currency": "brl",
"network": "pix"
},
"destination": {
"type": "wallet",
"id": "{{wallet_id}}",
"currency": "usdc",
"network": "polygon"
},
"integratorBpsFeeRate": "50"
}'
```
Sends USDC from a Polygon wallet and delivers BRL to a PIX bank account.
```bash theme={"dark"}
curl -X POST https://api.spherepay.co/v2/transfer \
-H "Authorization: Bearer {{api_key}}" \
-H "Content-Type: application/json" \
-d '{
"amount": "500.00",
"customer": "{{customer_id}}",
"source": {
"type": "wallet",
"id": "{{wallet_id}}",
"currency": "usdc",
"network": "polygon"
},
"destination": {
"type": "bank_account",
"id": "{{bank_account_id}}",
"currency": "brl",
"network": "pix"
},
"integratorBpsFeeRate": "50"
}'
```
### Response
A successful request returns a transfer object with an `id`, `type`, and initial `status`:
```json theme={"dark"}
{
"id": "payout_1234567890abcdef12345678",
"type": "on_ramp",
"status": "pendingFunding",
"customer": "customer_123abc..."
}
```
Store the `id` — you will use it to poll for status updates.
## BRL/PIX transfer constraints
BRL transfers use Brazil's PIX instant payment network and are subject to additional rules beyond standard USD/EUR transfers.
**Transfer limits**
* On-ramp (PIX BRL → USDC/USDT): R$1.00 – R$7,500.00 per transfer
* Off-ramp (USDC/USDT → PIX BRL): $1.00 – $7,500.00 (stablecoin units, approximately 1:1 with USD)
Limits may be increased based on due diligence. Contact your SpherePay representative for details.
**Supported wallet networks**
BRL transfers support `polygon`, `ethereum`, `base`, and `tron`. Solana is **not supported** for BRL transfers — requests that pass `"network": "sol"` will be rejected.
**Supported stablecoins**
| Stablecoin | Polygon | Ethereum | Base | Tron |
| - | - | - | - | - |
| USDC | ✅ | ✅ | ✅ | — |
| USDT | ✅ | ✅ | — | ✅ |
USDT on Base and USDC on Tron are not supported for BRL transfers.
**Fee parameter**
Only `integratorBpsFeeRate` (basis points) is accepted for BRL transfers. `integratorFixedFee` is not allowed because fees are collected on the stablecoin side, and a fiat-denominated fixed fee would require cross-currency conversion.
**Verification profile**
BRL transfers require a separate verification profile (`kyc_profile_b` for individuals, `kyb_profile_b` for businesses) in addition to the standard profile. Contact your SpherePay representative to enable access.
## Track transfers
### List all transfers
`GET https://api.spherepay.co/v2/transfer`
Returns a paginated list of transfers for your account. See the [List Transfers API reference](/api-reference/transfer/get) for query parameters and response shape.
### Retrieve a transfer by ID
`GET https://api.spherepay.co/v2/transfer/{id}`
Returns the current state of a single transfer. Poll this endpoint to track progress. See the [Get a Transfer API reference](/api-reference/transfer/get-id) for the full response schema.
Poll `GET /v2/transfer/{id}` after creation and after each status update until the transfer reaches a terminal state: `succeeded`, `refunded`, `failed`, or `canceled`. See [Transfer Lifecycle](/concepts/transfers/lifecycle) for the full status reference.
# Supported Payment Rails and Stablecoins
Source: https://docs.spherepay.co/concepts/transfers/supported-rails
View all supported source and destination rails, currencies, and stablecoin networks for SpherePay Transfers, Onramper Accounts, and Offloader Wallets.
SpherePay supports a broad range of fiat payment rails and stablecoin networks, enabling on-ramp and off-ramp transfers across USD, EUR, and BRL on the fiat side and USDC, USDT, and EURC on the crypto side. The table below lists supported source-to-destination combinations. Use it as a reference when planning your integration. The **Verification profile** column names the profile a customer needs `approved` for that route — see [Verification profiles](/concepts/onboarding/verification-profile).
**Some combinations may not be available.** Availability can depend on your account configuration, region, and verification profile, and the supported set differs by product — some combinations apply only to the [Transfers API](/api-reference/transfer/post), [Onramper Accounts](/concepts/automation/onramper-accounts), or [Offloader Wallets](/concepts/automation/offloader-wallets). Confirm availability with your SpherePay representative before building.
## Supported rails table
| Source Rail | Source Currency | Destination Rail | Destination Currency | Verification profile |
| - | - | - | - | - |
| ACH | USD | Ethereum | USDT | A |
| ACH | USD | Tron | USDT | A |
| ACH | USD | Arbitrum | USDC | A |
| ACH | USD | Avalanche | USDC | A |
| ACH | USD | Base | USDC | A |
| ACH | USD | Ethereum | USDC | A |
| ACH | USD | Polygon | USDC | A |
| ACH | USD | Solana | USDC | A |
| ACH | USD | Base | EURC | A |
| ACH | USD | Ethereum | EURC | A |
| ACH | USD | Solana | EURC | A |
| Wire | USD | Ethereum | USDT | A |
| Wire | USD | Tron | USDT | A |
| Wire | USD | Arbitrum | USDC | A |
| Wire | USD | Avalanche | USDC | A |
| Wire | USD | Base | USDC | A |
| Wire | USD | Ethereum | USDC | A |
| Wire | USD | Polygon | USDC | A |
| Wire | USD | Solana | USDC | A |
| Wire | USD | Base | EURC | A |
| Wire | USD | Ethereum | EURC | A |
| Wire | USD | Solana | EURC | A |
| SEPA | EUR | Ethereum | USDT | A |
| SEPA | EUR | Tron | USDT | A |
| SEPA | EUR | Arbitrum | USDC | A |
| SEPA | EUR | Avalanche | USDC | A |
| SEPA | EUR | Base | USDC | A |
| SEPA | EUR | Ethereum | USDC | A |
| SEPA | EUR | Polygon | USDC | A |
| SEPA | EUR | Solana | USDC | A |
| SEPA | EUR | Base | EURC | A |
| SEPA | EUR | Ethereum | EURC | A |
| SEPA | EUR | Solana | EURC | A |
| PIX | BRL | Ethereum | USDT | B |
| PIX | BRL | Polygon | USDT | B |
| PIX | BRL | Tron | USDT | B |
| PIX | BRL | Base | USDC | B |
| PIX | BRL | Ethereum | USDC | B |
| PIX | BRL | Polygon | USDC | B |
| SPEI | MXN | SWIFT | USD | D (with C) |
| SPEI | MXN | Wire | USD | D (with C) |
| Arbitrum | USDC | ACH | USD | A |
| Arbitrum | USDC | Wire | USD | A |
| Arbitrum | USDC | SEPA | EUR | A |
| Avalanche | USDC | ACH | USD | A |
| Avalanche | USDC | Wire | USD | A |
| Avalanche | USDC | SEPA | EUR | A |
| Base | USDC | ACH | USD | A |
| Base | USDC | Wire | USD | A |
| Base | USDC | SEPA | EUR | A |
| Base | USDC | PIX | BRL | B |
| Base | EURC | ACH | USD | A |
| Base | EURC | Wire | USD | A |
| Base | EURC | SEPA | EUR | A |
| Ethereum | USDC | ACH | USD | A |
| Ethereum | USDC | Wire | USD | A or C |
| Ethereum | USDC | SWIFT | USD | C |
| Ethereum | USDC | SEPA | EUR | A |
| Ethereum | USDC | PIX | BRL | B |
| Ethereum | USDT | ACH | USD | A |
| Ethereum | USDT | Wire | USD | A or C |
| Ethereum | USDT | SWIFT | USD | C |
| Ethereum | USDT | PIX | BRL | B |
| Ethereum | EURC | ACH | USD | A |
| Ethereum | EURC | Wire | USD | A |
| Ethereum | EURC | SEPA | EUR | A |
| Polygon | USDC | ACH | USD | A |
| Polygon | USDC | Wire | USD | A |
| Polygon | USDC | SEPA | EUR | A |
| Polygon | USDC | PIX | BRL | B |
| Polygon | USDT | PIX | BRL | B |
| Solana | USDC | ACH | USD | A |
| Solana | USDC | Wire | USD | A or C |
| Solana | USDC | SWIFT | USD | C |
| Solana | USDC | SEPA | EUR | A |
| Solana | EURC | ACH | USD | A |
| Solana | EURC | Wire | USD | A |
| Solana | EURC | SEPA | EUR | A |
| Tron | USDT | ACH | USD | A |
| Tron | USDT | Wire | USD | A or C |
| Tron | USDT | SWIFT | USD | C |
| Tron | USDT | PIX | BRL | B |
## Notes
**Sender-name visibility depends on the rail.** Named payouts — where the sender's business name appears on the receiving bank statement — are supported on wire only. SpherePay cannot guarantee sender-name visibility on ACH because the receiving institution determines what displays for ACH credits. If the beneficiary requires an identifiable sender name, use `network: "wire"`.
BRL/PIX transfers require a separate verification profile (`kyc_profile_b` for individuals, `kyb_profile_b` for businesses) in addition to the standard profile. Contact your SpherePay representative to enable BRL/PIX access before initiating transfers on that rail.
* **Each rail has its own optional reference field** on the transfer's `destination`: `achReference` for ACH, `wireMessage` for wire, and `sepaReference` (6–140 characters) for SEPA. Send only the field that matches the destination `network` — the API rejects the request if you send a field for another rail. See [Create Transfer](/api-reference/transfer/post).
* **Solana is not supported as a source or destination for BRL transfers.** Requests using `"network": "sol"` with a BRL source or destination will be rejected.
* The table above reflects the Transfers API. Onramper Accounts and Offloader Wallets may support a different subset of combinations.
* If you need a combination that is not listed, or have questions about eligibility, contact your SpherePay representative.
**SWIFT (international USD payout)** is supported as an off-ramp destination rail (`currency: "usd"`, `networks: ["swift"]`) for delivering USD to international bank accounts. Register the beneficiary as a USD SWIFT bank account with an `accountNumber` (IBAN or BBAN) and `bic`, plus up to three optional intermediary correspondent-bank BICs. SWIFT requires **verification profile C** — a USD SWIFT bank account cannot be created until profile C is approved. Third-party SWIFT payments also require a supporting document — see [Supporting documents for SWIFT third-party payments](/implementation-guides/third-party-flows#supporting-documents-for-swift-third-party-payments).
**SPEI (MXN on-ramp)** is supported as a source rail for converting MXN into USD delivered over SWIFT or Wire. SPEI requires **verification profile D**, which is enabled together with profile C. MXN to USD is the only supported direction.
# Register Crypto Wallets for Stablecoin Transfers
Source: https://docs.spherepay.co/concepts/transfers/wallets
Link a customer's existing blockchain wallet address to SpherePay to enable on-ramp and off-ramp transfers across supported stablecoin networks.
A wallet in SpherePay is a registration of your customer's existing blockchain wallet address. SpherePay does not create or custody wallets — it links an address your customer already owns to their SpherePay profile so transfers can be routed to or from that address. You must register at least one wallet before creating any on-ramp or off-ramp transfer that involves crypto.
SpherePay registers an existing wallet address — it does not generate or host a new wallet on your customer's behalf. The address must already exist on the target network before registration.
## Register a wallet
`POST https://api.spherepay.co/v2/wallet`
For the full request and response schema, see the [Create Wallet API reference](/api-reference/wallet/post).
## Supported networks
| Network | Network Code | USDC | USDT | EURC |
| - | - | - | - | - |
| Solana | `sol` | ✅ | — | ✅ |
| Ethereum | `ethereum` | ✅ | ✅ | ✅ |
| Tron | `tron` | — | ✅ | — |
| Polygon | `polygon` | ✅ | ✅ | — |
| Base | `base` | ✅ | — | ✅ |
| Arbitrum | `arbitrum` | ✅ | — | — |
| Avalanche | `avalanche` | ✅ | — | — |
| Starknet | `starknet` | ✅ | — | — |
Solana is not supported as the destination network for BRL/PIX on-ramp transfers. Use `polygon`, `ethereum`, `base`, or `tron` when the source currency is BRL. See [BRL/PIX transfer constraints](/concepts/transfers/overview#brlpix-transfer-constraints).
## Request example
Pass the customer ID, the network code, and the on-chain address to register the wallet.
```json theme={"dark"}
{
"customerId": "customer_1234567890",
"network": "sol",
"address": "0x1234567890123456789012345678901234567890"
}
```
## Response example
```json theme={"dark"}
{
"id": "wallet_1234567890",
"address": "0x1234567890123456789012345678901234567890",
"network": "sol",
"customerId": "customer_1234567890",
"created": "2026-01-25T12:00:00.000Z",
"updated": "2026-01-25T12:00:00.000Z"
}
```
## Using the returned ID
The returned `id` is your persistent reference to this wallet. Store it against the customer record and pass it as the `source.id` or `destination.id` when creating transfers. You do not need to re-register the same address for subsequent transfers — reuse the stored `id`.
# Webhook Event Catalog
Source: https://docs.spherepay.co/concepts/webhooks/event-catalog
Every webhook event subject you can subscribe to — customer and transfer events, with their triggers and example payloads.
This page lists every event you can pass in `subscribedEvents` when [registering a webhook endpoint](/concepts/webhooks/managing-endpoints). Event names follow the `{resource}.{subject}` convention. Events fire on transitions of the resource's publicly observable status — internal-only states never produce webhook events.
## Wildcards
In addition to individual event names, `subscribedEvents` accepts:
| Value | Subscribes to |
| - | - |
| `*` | Every event |
| `customer.*` | All customer events |
| `transfer.*` | All transfer events |
## Customer events
Fired as a customer's [verification profile](/concepts/onboarding/verification-profile) moves through its status lifecycle. One event fires per verification profile per transition — a customer with multiple profiles produces an independent event stream per profile.
| Event | Trigger |
| - | - |
| `customer.created` | `POST /v2/customer` succeeds. `previousStatus` is `null`, `status` is `incomplete`, and `verificationProfile` is `null` (the event marks entity creation, not a profile transition). |
| `customer.pending` | All `required` criteria items resolve for a verification profile. |
| `customer.approved` | SpherePay completes review and approves the verification profile. |
| `customer.rejected` | SpherePay completes review and rejects the verification profile. |
Example — `customer.approved`:
```json theme={"dark"}
{
"id": "event_01HXP9ZK7Q4ABC123",
"type": "customer.approved",
"apiVersion": "v2",
"originalCreateDate": "2026-08-07T14:00:00.123456Z",
"livemode": true,
"sequence": 3,
"data": {
"id": "customer_f31121c389624d3697cbf3ea8830b7a4",
"type": "customer",
"applicationId": "application_1324354657",
"verificationProfile": "kyc_profile_a",
"previousStatus": "pending",
"previousStatusAt": "2026-08-06T10:15:30.000Z",
"status": "approved",
"statusAt": "2026-08-07T14:00:00.000Z"
}
}
```
## Transfer events
Fired as a [transfer](/concepts/transfers/lifecycle) moves through its status lifecycle. Webhooks are now the recommended way to track transfer progress.
| Event | Trigger |
| - | - |
| `transfer.created` | `POST /v2/transfer` succeeds. `previousStatus` is `null`; `status` is the initial state (typically `pendingFunding`). |
| `transfer.pendingFunding` | The transfer returned to `pendingFunding` — for example, after clearing `pendingReview`. The initial `pendingFunding` state is covered by `transfer.created`, not a separate event. |
| `transfer.pendingReview` | The transfer entered `pendingReview` — the payment may require additional compliance review. |
| `transfer.fundsReceived` | SpherePay detected funds from the customer. |
| `transfer.processing` | Processing started. |
| `transfer.succeeded` | Terminal success — funds delivered to the destination. |
| `transfer.returned` | The destination bank returned the transfer. |
| `transfer.pendingRefundInformation` | SpherePay needs information from the customer to complete a refund. |
| `transfer.refunded` | The refund was delivered to the customer. |
| `transfer.failed` | Terminal failure. |
| `transfer.canceled` | The customer canceled before funding. |
| `transfer.expired` | The transfer expired before funding. |
| `transfer.failedPrecondition` | Customer-provided information failed a precondition check. |
| `transfer.unexpectedError` | An unexpected error occurred during processing. |
Example — `transfer.succeeded`:
```json theme={"dark"}
{
"id": "event_01HXPA3M9R7DEF456",
"type": "transfer.succeeded",
"apiVersion": "v2",
"originalCreateDate": "2026-08-07T14:05:30.000Z",
"livemode": true,
"sequence": 5,
"data": {
"id": "payout_d243ab2b1de4447d8a046d87fefe58cf",
"type": "transfer",
"applicationId": "application_1324354657",
"customerId": "customer_f31121c389624d3697cbf3ea8830b7a4",
"transferType": "oneTimeTransfer",
"previousStatus": "processing",
"previousStatusAt": "2026-08-07T14:02:15.000Z",
"status": "succeeded",
"statusAt": "2026-08-07T14:05:30.000Z"
}
}
```
Transfer event names use the public API resource name (`transfer.*`), while `data.id` uses the `payout_` prefix — matching the IDs returned by the `/v2/transfer` API today. `transferType` is `staticTransfer` for on-ramper accounts and offloader wallets, `oneTimeTransfer` otherwise. The deprecated `undeliverable` status does not produce events.
# Webhook Event Payloads
Source: https://docs.spherepay.co/concepts/webhooks/event-payloads
The structure of every webhook payload — the envelope fields, how the sequence field orders events, and how applicationId scopes each event.
Every webhook delivery carries a JSON body with the same envelope structure, regardless of event type. This page explains each field and the two most important ones to get right in your integration: `sequence` and `applicationId`.
## The envelope
```json theme={"dark"}
{
"id": "event_01HXPA3M9R7DEF456",
"type": "transfer.succeeded",
"apiVersion": "v2",
"originalCreateDate": "2026-08-07T14:05:30.000Z",
"livemode": true,
"sequence": 5,
"data": {
"id": "payout_d243ab2b1de4447d8a046d87fefe58cf",
"type": "transfer",
"applicationId": "application_1324354657",
"customerId": "customer_f31121c389624d3697cbf3ea8830b7a4",
"transferType": "oneTimeTransfer",
"previousStatus": "processing",
"previousStatusAt": "2026-08-07T14:02:15.000Z",
"status": "succeeded",
"statusAt": "2026-08-07T14:05:30.000Z"
}
}
```
| Field | Description |
| - | - |
| `id` | Unique event identifier (`event_` prefix). Stable across every delivery and replay of the same event — use it as your idempotency key. |
| `type` | The event name from the [event catalog](/concepts/webhooks/event-catalog), e.g. `customer.approved`. |
| `apiVersion` | The version of the event schema (currently `v2`). This is distinct from the dated `apiVersion` you set on the webhook endpoint. |
| `originalCreateDate` | ISO 8601 timestamp of when the resource state change that triggered this event originally occurred. Never changes, even on replays — unlike the `Sphere-Timestamp` header, which reflects when each delivery attempt was sent. |
| `livemode` | `true` for production events, `false` for test-mode events. |
| `sequence` | Monotonically increasing integer used to order events for a resource. See [below](#the-sequence-field). |
| `data` | The subject of the event: the resource's ID, its `applicationId`, the previous status, and the new status. Fields vary slightly by resource — see the [event catalog](/concepts/webhooks/event-catalog). |
The body is byte-for-byte identical across every delivery and replay of the same event. Anything that varies per delivery attempt — the delivery ID, attempt count, timestamp, signature — lives in the [HTTP headers](/concepts/webhooks/verifying-signatures#delivery-headers) instead.
## Events, deliveries, and attempts
Three distinct records stand behind every webhook that reaches you, and each carries its own identifier and its own timestamps. Keeping them straight is the key to reconciling the various timestamps and the `sequence` logic below.
| Record | What it is | Identifier | Timestamps | Where you see it |
| - | - | - | - | - |
| **Event** | The immutable fact that something happened. One per occurrence, shared by every endpoint that receives it. | `id` in the body (`event_...`) | `originalCreateDate` — when the resource state change that triggered the event originally occurred. Never changes, even on replay. | The payload body; [`GET /v2/events`](/concepts/webhooks/events-and-replays) |
| **Event delivery** | The routing of one event to one endpoint. Two subscribed endpoints produce two deliveries of the same event. | `Sphere-Delivery-Id` header (`eventDelivery_...`) | `deliveredAt` — when your endpoint acknowledged with a `2xx`. | The headers; the `delivery` object in the Events API |
| **Delivery attempt** | A single HTTP request to your endpoint — the original send, or a manual replay. | `Sphere-Delivery-Attempt` counter and `Sphere-Delivery-Type` header; `attempts[]` in the event detail | `Sphere-Timestamp` header / `attemptedAt` — when this particular request was sent. Fresh on every attempt. | The headers; [`GET /v2/events/{id}`](/concepts/webhooks/events-and-replays#retrieve-an-event) |
The split follows one rule: anything intrinsic to the **event** — `id`, `type`, `sequence`, `originalCreateDate`, `data` — lives in the body and never changes; anything describing **this particular HTTP request** — delivery ID, attempt number, attempt type, timestamp, signature — lives in the headers and is regenerated per attempt.
When ordering business state, trust the event, never the attempt. A replay of an old event arrives with a fresh `Sphere-Timestamp` (so it passes signature tolerance checks), but its `sequence` and `originalCreateDate` still reflect when the state change actually occurred. Ordering by `Sphere-Timestamp` would make a replayed old event look newer than events that superseded it — ordering by `sequence` keeps replays harmless.
## Payloads carry the state change, not the full resource
Webhook payloads deliberately contain the minimum you need to react: the resource ID, and the transition from `previousStatus` to `status`. They do **not** contain the full resource object.
Most reactions — routing on the new status, updating your own database row, notifying your operations team — work from the payload alone. When you need the complete resource (full verification criteria, bank account details, transfer amounts), fetch it from the resource's `GET` endpoint:
```bash theme={"dark"}
# The payload told you payout_d243... succeeded; fetch the full object:
curl https://api.spherepay.co/v2/transfer/payout_d243ab2b1de4447d8a046d87fefe58cf \
-H "Authorization: Bearer {{api_key}}"
```
Because the payload is minimal and immutable, a replayed event never carries stale data pretending to be fresh — it describes a transition that happened at `originalCreateDate`. If you always `GET` the resource for its current state before acting on side effects, replays are harmless.
## The `sequence` field
`sequence` is a monotonically increasing integer **scoped to a single resource instance** — one counter per customer, one per transfer, and so on, keyed by `data.id`. It is assigned when the event is created and never changes, so it reflects the true order in which state changes occurred even when deliveries arrive out of order.
Because delivery order is not guaranteed, use `sequence` to guard against stale updates:
1. Store the highest `sequence` you have processed **per resource instance** — for example, a `lastSphereSequence` column on the customer row in your own database.
2. When an event arrives, compare its `sequence` to the stored value for that `data.id`.
3. If `incoming.sequence <= stored.sequence`, the event is stale or a duplicate — acknowledge with a `2xx` and skip the update.
4. Otherwise, apply the update and store the new `sequence`.
You do not need a global counter across your account — just per-entity tracking, colocated with the resource state you already maintain.
Do not assume sequence values you receive are contiguous. If your endpoint subscribes to a subset of a resource's events, you will observe gaps — use the `<=` staleness comparison above, never gap detection.
### Why both `sequence` and `originalCreateDate`?
They answer different questions. `originalCreateDate` tells you **when** a state change happened — wall-clock time, useful for display, audit trails, and time-window queries. But wall-clock time is an unreliable *ordering* key: two rapid transitions on the same resource can land close enough together that their timestamps collide, and a timestamp comparison has no way to break the tie. `sequence` exists to make ordering unambiguous — a strictly increasing integer per resource instance, so any two events for the same resource always have a definite order, no matter how close together they occurred.
Rule of thumb: **order and deduplicate by `sequence`; display and audit by `originalCreateDate`.**
## The `applicationId` field
Every payload includes `data.applicationId` — the SpherePay application the event belongs to. Webhook endpoints are registered per application, and events are only ever delivered for resources owned by the endpoint's application.
This matters most when you operate **multiple SpherePay applications that share one webhook receiver URL**. Registering the same URL under two applications is perfectly valid — but your handler then receives events from both, and each application's endpoint has its own signing secret. Use `applicationId` to route each event to the right application context in your system (and to select the right secret when verifying signatures).
You can find each application's ID on the **Settings** page of the SpherePay integrator dashboard.
# Browsing Events and Replaying Deliveries
Source: https://docs.spherepay.co/concepts/webhooks/events-and-replays
Look through every event SpherePay has recorded, inspect delivery attempts and responses, and replay a failed delivery on demand.
Every event SpherePay creates — and every attempt to deliver it — is recorded and queryable. Use the Events API to audit what happened, diagnose failed deliveries, and re-send any delivery to your endpoint. Dashboard views for browsing and replaying events are coming soon; today these controls live in the API.
## List events
```bash theme={"dark"}
curl "https://api.spherepay.co/v2/events?type=transfer.*&status=failed&page=1&limit=10" \
-H "Authorization: Bearer {{api_key}}"
```
| Parameter | Description |
| - | - |
| `type` | Event type filter — an exact name (`transfer.succeeded`), a resource wildcard (`transfer.*`), or `*`. |
| `status` | Filter by delivery status: `queued`, `delivering`, `delivered`, or `failed`. |
| `webhookEndpointId` | Only events with a delivery to the given endpoint. |
| `createdStart` / `createdEnd` | Inclusive ISO 8601 bounds on event creation time. |
| `page` / `limit` | Pagination (defaults `1` / `10`, maximum limit `100`). |
Each list item summarizes the event and its most recent delivery:
```json theme={"dark"}
{
"data": [
{
"id": "event_01HXPA3M9R7DEF456",
"type": "transfer.succeeded",
"createdAt": "2026-08-07T14:05:30.123Z",
"sequence": 5,
"delivery": {
"id": "eventDelivery_2351653173",
"webhookEndpointId": "webhook_01HXP9TN3J2ABC",
"status": "delivered",
"attemptsUsed": 1,
"deliveredAt": "2026-08-07T14:05:31.821Z",
"latestAttempt": {
"type": "original",
"success": true,
"responseCode": 200
}
}
}
],
"page": 1,
"limit": 10,
"total": 1,
"totalPages": 1,
"hasNext": false,
"hasPrevious": false
}
```
To find everything that needs attention, filter by `status=failed`. A `responseCode` of `null` on an attempt means your endpoint did not respond at all — a timeout or network error rather than an HTTP error.
### Delivery statuses
| Status | Meaning |
| - | - |
| `queued` | The delivery is waiting to be sent. |
| `delivering` | An HTTP request to your endpoint is in flight. |
| `delivered` | Your endpoint acknowledged with a `2xx` response. |
| `failed` | The most recent attempt failed — non-`2xx` response, or no response within 15 seconds. Recoverable via replay. |
## Retrieve an event
`GET /v2/events/{id}` returns the full event plus every delivery and every attempt — including the response your endpoint returned:
```bash theme={"dark"}
curl https://api.spherepay.co/v2/events/event_01HXPA3M9R7DEF456 \
-H "Authorization: Bearer {{api_key}}"
```
Each attempt records `type` (`original` or `replay`), `attemptedAt`, `success`, `responseCode`, `responseBody` (truncated to 200 characters, captured on failures to help you debug), and `latencyMs`.
If you are holding a `Sphere-Delivery-Id` header from a delivery you received, that is the delivery's ID — you can use it to correlate a request hitting your servers with the delivery and attempt records shown here.
## Replaying a delivery
SpherePay makes exactly one automatic delivery attempt per event per endpoint — there are **no automatic retries**. When a delivery fails (your endpoint was down, timed out, or returned an error), you decide when to re-send it with `POST /v2/events/replay/{eventDeliveryId}`, using the delivery ID (not the event ID):
```bash theme={"dark"}
curl -X POST https://api.spherepay.co/v2/events/replay/eventDelivery_2351653173 \
-H "Authorization: Bearer {{api_key}}"
```
A replay re-sends the **byte-for-byte identical payload** to the same endpoint, with a fresh `Sphere-Timestamp` and `Sphere-Signature` and the headers `Sphere-Delivery-Type: replay` and `Sphere-Replay-Reason: manual`. Your verification code path is identical for originals and replays.
A successful replay transitions the delivery to `delivered`; a failed replay leaves it `failed` and records another attempt. Either way, the full attempt history is preserved.
Replays are delivered at-least-once on top of whatever your endpoint already received, and you can replay a delivery that already succeeded. Make sure your handler deduplicates on the event `id` and applies the [`sequence` staleness check](/concepts/webhooks/event-payloads#the-sequence-field) so replays are always safe.
## Recovering from downtime
If your endpoint was down for a period:
1. List events with `status=failed` and `createdStart`/`createdEnd` covering the outage window.
2. Replay each failed delivery via `POST /v2/events/replay/{eventDeliveryId}`.
3. Your `sequence` handling will automatically discard any replayed event that has since been superseded.
Alternatively, because payloads are minimal, you can simply re-fetch the current state of affected resources with their `GET` endpoints and reconcile directly.
# Managing Webhook Endpoints
Source: https://docs.spherepay.co/concepts/webhooks/managing-endpoints
Register webhook endpoints, subscribe to events, and understand endpoint limits, states, and the delete-and-recreate update model.
A webhook endpoint is a URL you register with SpherePay together with the list of events it should receive. This page covers the full endpoint lifecycle: creating an endpoint, listing and retrieving endpoints, and deleting them.
## Register an endpoint
Create an endpoint with `POST /v2/webhook-endpoints`:
```bash theme={"dark"}
curl -X POST https://api.spherepay.co/v2/webhook-endpoints \
-H "Authorization: Bearer {{api_key}}" \
-H "Content-Type: application/json" \
-d '{
"url": "https://api.example.com/sphere-webhook",
"subscribedEvents": ["customer.approved", "customer.rejected", "transfer.*"],
"apiVersion": "2026-08-01",
"description": "Production onboarding and transfer events",
"metadata": { "team": "payments", "environment": "prod" }
}'
```
| Field | Required | Description |
| - | - | - |
| `url` | Yes | The URL SpherePay delivers events to. Must be HTTPS in live mode. Maximum 2,048 characters. |
| `subscribedEvents` | Yes | One or more event names from the [event catalog](/concepts/webhooks/event-catalog), or a wildcard: `*` (all events) or a resource wildcard such as `customer.*` or `transfer.*`. |
| `apiVersion` | Yes | A `YYYY-MM-DD` API version date. Must be a valid calendar date and must not be in the future. |
| `description` | No | Free-text label, up to 500 characters. |
| `metadata` | No | Key-value pairs for your own bookkeeping — for example, to track registered endpoints within your system. |
The response returns the endpoint and its signing secret:
```json theme={"dark"}
{
"id": "webhook_01HXP9TN3J2ABC",
"url": "https://api.example.com/sphere-webhook",
"subscribedEvents": ["customer.approved", "customer.rejected", "transfer.*"],
"status": "enabled",
"apiVersion": "2026-08-01",
"secret": "whsec_9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c",
"description": "Production onboarding and transfer events",
"metadata": { "team": "payments", "environment": "prod" },
"createdAt": "2026-08-07T10:00:00Z",
"updatedAt": "2026-08-07T10:00:00Z"
}
```
The `secret` (prefixed `whsec_`) is returned **exactly once**, in the creation response. It is never returned by `GET` requests and cannot be retrieved later. Store it securely — you need it to [verify the signature](/concepts/webhooks/verifying-signatures) on every delivery. Each endpoint has its own distinct secret; secrets are never shared across endpoints.
Newly created endpoints are `enabled` immediately and begin receiving events that match their subscription.
## Updating an endpoint: delete and recreate
There is currently **no update operation** for webhook endpoints. To change an endpoint's URL, subscribed events, or any other property, delete the endpoint and create a new one. The same applies to rotating a signing secret — deleting an endpoint invalidates its secret, and the replacement endpoint receives a fresh one.
In-place endpoint updates are coming soon — a future release will let you modify an existing endpoint's URL and subscriptions without recreating it. Until then, use the delete-and-recreate flow below.
For a zero-gap migration:
1. Create the new endpoint with the updated configuration. Both endpoints now receive events.
2. Confirm the new endpoint is receiving and verifying deliveries.
3. Delete the old endpoint.
During step 1 both endpoints receive matching events, so your handlers should be idempotent — deduplicate on the event `id` in the payload body.
## List and retrieve endpoints
```bash theme={"dark"}
curl "https://api.spherepay.co/v2/webhook-endpoints?page=1&limit=10" \
-H "Authorization: Bearer {{api_key}}"
```
List query parameters:
| Parameter | Description |
| - | - |
| `page` | Page number, starting at `1` (default `1`). |
| `limit` | Results per page (default `10`, maximum `100`). |
| `status` | Filter by endpoint state: `enabled`, `disabled`, or `errored`. |
| `subscribedEvents` | Comma-separated event names; returns endpoints subscribed to at least one of them. Wildcard values (`*`, `customer.*`, `transfer.*`) are accepted. |
The `subscribedEvents` filter matches **literally** against each endpoint's subscription list — it does not expand wildcards. Filtering by `transfer.*` returns endpoints that subscribed with the `transfer.*` wildcard itself, not endpoints subscribed to individual events like `transfer.succeeded` (and vice versa). To find every endpoint that would receive a given event, filter by both forms: `subscribedEvents=transfer.succeeded,transfer.*,*`.
List responses use SpherePay's standard pagination envelope: `{ "data": [...], "page", "limit", "total", "totalPages", "hasNext", "hasPrevious" }`.
Retrieve a single endpoint with `GET /v2/webhook-endpoints/{id}`. The `secret` field is never included.
## Delete an endpoint
```bash theme={"dark"}
curl -X DELETE https://api.spherepay.co/v2/webhook-endpoints/{{webhook_endpoint_id}} \
-H "Authorization: Bearer {{api_key}}"
```
Deletion returns `204 No Content`, transitions the endpoint to the `deleted` state, invalidates its signing secret, and stops all future deliveries. **This is terminal and cannot be undone** — to resume delivery, create a new endpoint.
## Endpoint limits
Each application may have at most **6 active webhook endpoints** — endpoints in the `enabled`, `disabled`, or `errored` state. Deleted endpoints do not count toward the limit. Exceeding it returns `409 Conflict`.
## Endpoint states
| State | Meaning |
| - | - |
| `enabled` | Receiving deliveries. The initial state after registration. |
| `disabled` | Not receiving deliveries. |
| `errored` | Reserved for delivery-failure handling in a future release. |
| `deleted` | Terminal. The endpoint no longer exists for delivery purposes and its secret is invalid. |
Only `enabled` endpoints receive deliveries.
**Failed deliveries do not disable your endpoint.** In the current release, SpherePay never changes an endpoint's state based on delivery outcomes: if your endpoint returns a non-`2xx` response or times out, the individual delivery is marked `failed` and no automatic retry occurs, but the endpoint stays `enabled` and continues receiving subsequent events. Recover missed events with a [manual replay](/concepts/webhooks/events-and-replays#replaying-a-delivery).
## Errors
| Condition | HTTP status | Description |
| - | - | - |
| Unknown event name in `subscribedEvents` | `400` | The event type is not in the published catalog. |
| Invalid `apiVersion` | `400` | Not a valid `YYYY-MM-DD` calendar date, or a date in the future. |
| Endpoint not found | `404` | The webhook endpoint ID does not exist. |
| Endpoint limit exceeded | `409` | The application already has 6 active endpoints. |
# Webhooks Overview
Source: https://docs.spherepay.co/concepts/webhooks/overview
Receive real-time notifications when customers, transfers, and other SpherePay resources change state — instead of polling for updates.
Webhooks let SpherePay notify your application the moment something happens — a customer is approved, a transfer succeeds, funds are received — so you no longer have to check `GET` endpoints for changes. You register an HTTPS endpoint, choose the events you care about, and SpherePay delivers a signed JSON payload to that endpoint every time one of those events occurs.
## How it works
1. **Register a webhook endpoint.** Call [`POST /v2/webhook-endpoints`](/concepts/webhooks/managing-endpoints) with your URL and the list of events you want to subscribe to. The response includes a signing secret — store it securely; it is shown exactly once.
2. **An event occurs.** A resource in your application changes state — for example, a transfer moves from `processing` to `succeeded`.
3. **SpherePay delivers the event.** SpherePay sends an HTTP `POST` to every enabled endpoint subscribed to that event type. The request carries a signed payload describing the state transition.
4. **You verify and process.** Your handler [verifies the signature](/concepts/webhooks/verifying-signatures), applies the state change, and responds with a `2xx` status code within 15 seconds.
## Core concepts
SpherePay's webhook system tracks three distinct records for every notification, and all three are visible through the [Events API](/concepts/webhooks/events-and-replays):
| Concept | Meaning |
| - | - |
| **Event** | A fact about something that happened in SpherePay — one per occurrence, immutable once created. Identified by an `event_` ID in the payload body. |
| **Event delivery** | One event being routed to one of your webhook endpoints. If two endpoints subscribe to the same event type, one event produces two deliveries. |
| **Delivery attempt** | A single HTTP request from SpherePay to your endpoint. Each attempt records the response code, latency, and whether it succeeded. |
Each record carries its own identifier and timestamps — see [Events, deliveries, and attempts](/concepts/webhooks/event-payloads#events-deliveries-and-attempts) for how they map to the payload body and headers, and which ones to trust when ordering state.
## Delivery semantics
Understanding these guarantees is essential to building a reliable consumer:
* **At-least-once delivery.** You may occasionally receive the same event more than once. Deduplicate using the event `id` in the body — it is stable across every delivery and replay of the same event.
* **No automatic retries.** SpherePay makes exactly one delivery attempt per event per endpoint. If your endpoint returns a non-`2xx` response, or does not respond within **15 seconds**, the delivery is marked `failed` and SpherePay does not retry it automatically. You can [replay a failed delivery](/concepts/webhooks/events-and-replays#replaying-a-delivery) at any time via the Events API.
* **Delivery order is not guaranteed.** Replays, parallel workers, and network jitter can all cause out-of-order arrival. Every payload carries a [`sequence` field](/concepts/webhooks/event-payloads#the-sequence-field) so you can detect and discard stale updates.
* **Minimal payloads.** Event payloads describe the state transition — the previous status and the new status — not the full resource. When you need the complete object, call the resource's `GET` endpoint. See [Event payloads](/concepts/webhooks/event-payloads).
Because there are no automatic retries in the current release, make your webhook handler fast and resilient: acknowledge with a `2xx` immediately after verifying the signature, and do heavy processing asynchronously. A slow handler that exceeds the 15-second window will cause deliveries to be marked `failed`.
## Responding to a delivery
Return any `2xx` status code to acknowledge receipt. Anything else — including redirects, which SpherePay does not follow — marks the delivery as `failed`.
## Explore
Register, list, and delete webhook endpoints, and understand endpoint limits and states.
The envelope structure, the `sequence` field, and how `applicationId` scopes each event.
Every event subject you can subscribe to, with triggers and example payloads.
Authenticate deliveries with HMAC-SHA256 — with snippets in Python, JavaScript, Go, Java, and C#.
Browse your event history, inspect delivery attempts, and replay failed deliveries.
# Verifying Webhook Signatures
Source: https://docs.spherepay.co/concepts/webhooks/verifying-signatures
Authenticate every webhook delivery by validating its HMAC-SHA256 signature — with reference implementations in Python, JavaScript, Go, Java, and C#.
SpherePay signs every webhook delivery so you can confirm it genuinely came from SpherePay and was not tampered with in transit. SpherePay never sends an unsigned delivery. **Always verify the signature before processing a payload**, and reject anything that fails verification with a non-`2xx` response.
## Delivery headers
Every delivery attempt carries these headers:
| Header | Example | Description |
| - | - | - |
| `Sphere-Signature` | `5c8b4e2f9a...` | Hex-encoded HMAC-SHA256 signature of this delivery. |
| `Sphere-Timestamp` | `1786455602` | Unix timestamp (seconds) of when this attempt was sent. Signed as part of the HMAC input. |
| `Sphere-Delivery-Id` | `eventDelivery_2351653173...` | The event delivery this attempt belongs to. Use it to look up the delivery in the [Events API](/concepts/webhooks/events-and-replays). |
| `Sphere-Delivery-Attempt` | `1` | Attempt counter for this delivery, starting at `1`. |
| `Sphere-Delivery-Type` | `original` or `replay` | Whether this is the first attempt or a manual replay. |
| `Sphere-Replay-Reason` | `manual` | Present on replays only. |
| `User-Agent` | `Sphere-Webhooks/2.0` | Constant. |
| `Content-Type` | `application/json` | Constant. |
Headers describe the *delivery attempt*; the body describes the *event*. On a replay, `Sphere-Timestamp` and `Sphere-Signature` are freshly generated — so the same verification code path handles originals and replays — while the body remains byte-for-byte identical to the original.
## How the signature is computed
The signature is an HMAC-SHA256 digest, hex-encoded:
```
signature = hex( HMAC-SHA256( secret, "{timestamp}.{rawBody}" ) )
```
* `secret` — the endpoint's signing secret (`whsec_...`), returned once when you [created the endpoint](/concepts/webhooks/managing-endpoints#register-an-endpoint).
* `timestamp` — the value of the `Sphere-Timestamp` header.
* `rawBody` — the raw HTTP request body bytes, exactly as transmitted.
To verify: compute the same digest yourself and compare it to `Sphere-Signature` using a constant-time comparison.
Verify against the **raw request body**, not a re-serialized version. Parsing the JSON and re-encoding it can reorder keys or change whitespace, which changes the bytes and breaks verification. Most web frameworks require explicit configuration to expose the raw body — capture it before any JSON middleware runs.
### Guard against replay attacks
To prevent captured deliveries from being re-sent by a third party, reject deliveries whose `Sphere-Timestamp` is more than **5 minutes** old. Manual replays get a fresh timestamp and signature, so legitimate replays always pass this check.
## Reference implementations
Each snippet takes the raw body bytes, the two headers, and your endpoint secret, and returns whether the delivery is authentic.
```python Python theme={"dark"}
import hashlib
import hmac
import time
TOLERANCE_SECONDS = 300 # 5 minutes
def verify_sphere_signature(raw_body: bytes, timestamp: str, signature: str, secret: str) -> bool:
# Reject stale deliveries (possible replay attack)
if abs(time.time() - int(timestamp)) > TOLERANCE_SECONDS:
return False
signed_content = f"{timestamp}.".encode() + raw_body
expected = hmac.new(secret.encode(), signed_content, hashlib.sha256).hexdigest()
# Constant-time comparison
return hmac.compare_digest(expected, signature)
# Example (Flask): request.get_data() returns the raw body bytes
# is_valid = verify_sphere_signature(
# raw_body=request.get_data(),
# timestamp=request.headers["Sphere-Timestamp"],
# signature=request.headers["Sphere-Signature"],
# secret=os.environ["SPHERE_WEBHOOK_SECRET"],
# )
```
```javascript JavaScript theme={"dark"}
const crypto = require("crypto");
const TOLERANCE_SECONDS = 300; // 5 minutes
function verifySphereSignature(rawBody, timestamp, signature, secret) {
// Reject stale deliveries (possible replay attack)
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
if (!Number.isFinite(age) || age > TOLERANCE_SECONDS) {
return false;
}
const signedContent = `${timestamp}.${rawBody}`;
const expected = crypto
.createHmac("sha256", secret)
.update(signedContent)
.digest("hex");
// Constant-time comparison
const expectedBuf = Buffer.from(expected, "hex");
const receivedBuf = Buffer.from(signature, "hex");
if (expectedBuf.length !== receivedBuf.length) {
return false;
}
return crypto.timingSafeEqual(expectedBuf, receivedBuf);
}
// Example (Express): capture the raw body before JSON parsing
// app.use(express.json({ verify: (req, res, buf) => (req.rawBody = buf) }));
//
// const isValid = verifySphereSignature(
// req.rawBody,
// req.header("Sphere-Timestamp"),
// req.header("Sphere-Signature"),
// process.env.SPHERE_WEBHOOK_SECRET,
// );
```
```go Go theme={"dark"}
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"math"
"strconv"
"time"
)
const toleranceSeconds = 300 // 5 minutes
func verifySphereSignature(rawBody []byte, timestamp, signature, secret string) bool {
// Reject stale deliveries (possible replay attack)
ts, err := strconv.ParseInt(timestamp, 10, 64)
if err != nil || math.Abs(float64(time.Now().Unix()-ts)) > toleranceSeconds {
return false
}
mac := hmac.New(sha256.New, []byte(secret))
fmt.Fprintf(mac, "%s.", timestamp)
mac.Write(rawBody)
expected := hex.EncodeToString(mac.Sum(nil))
// Constant-time comparison
return hmac.Equal([]byte(expected), []byte(signature))
}
// Example (net/http): io.ReadAll(r.Body) returns the raw body bytes
// isValid := verifySphereSignature(
// rawBody,
// r.Header.Get("Sphere-Timestamp"),
// r.Header.Get("Sphere-Signature"),
// os.Getenv("SPHERE_WEBHOOK_SECRET"),
// )
```
```java Java theme={"dark"}
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.HexFormat;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public final class SphereWebhookVerifier {
private static final long TOLERANCE_SECONDS = 300; // 5 minutes
public static boolean verifySphereSignature(
byte[] rawBody, String timestamp, String signature, String secret)
throws Exception {
// Reject stale deliveries (possible replay attack)
long ts = Long.parseLong(timestamp);
if (Math.abs(System.currentTimeMillis() / 1000 - ts) > TOLERANCE_SECONDS) {
return false;
}
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
mac.update((timestamp + ".").getBytes(StandardCharsets.UTF_8));
mac.update(rawBody);
String expected = HexFormat.of().formatHex(mac.doFinal());
// Constant-time comparison
return MessageDigest.isEqual(
expected.getBytes(StandardCharsets.UTF_8),
signature.toLowerCase().getBytes(StandardCharsets.UTF_8));
}
// Example (Spring): bind the raw bytes before any JSON deserialization
// @PostMapping("/sphere-webhook")
// public ResponseEntity handle(@RequestBody byte[] rawBody,
// @RequestHeader("Sphere-Timestamp") String timestamp,
// @RequestHeader("Sphere-Signature") String signature) { ... }
}
```
```csharp C# theme={"dark"}
using System.Security.Cryptography;
using System.Text;
public static class SphereWebhookVerifier
{
private const int ToleranceSeconds = 300; // 5 minutes
public static bool VerifySphereSignature(
byte[] rawBody, string timestamp, string signature, string secret)
{
// Reject stale deliveries (possible replay attack)
if (!long.TryParse(timestamp, out var ts) ||
Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - ts) > ToleranceSeconds)
{
return false;
}
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var prefix = Encoding.UTF8.GetBytes($"{timestamp}.");
var signedContent = new byte[prefix.Length + rawBody.Length];
prefix.CopyTo(signedContent, 0);
rawBody.CopyTo(signedContent, prefix.Length);
var expected = Convert.ToHexString(hmac.ComputeHash(signedContent)).ToLowerInvariant();
// Constant-time comparison
return CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(expected),
Encoding.UTF8.GetBytes(signature.ToLowerInvariant()));
}
// Example (ASP.NET Core): read the raw body before model binding runs
// app.MapPost("/sphere-webhook", async (HttpRequest req) =>
// {
// using var ms = new MemoryStream();
// await req.Body.CopyToAsync(ms);
// var isValid = SphereWebhookVerifier.VerifySphereSignature(
// ms.ToArray(),
// req.Headers["Sphere-Timestamp"]!,
// req.Headers["Sphere-Signature"]!,
// Environment.GetEnvironmentVariable("SPHERE_WEBHOOK_SECRET")!);
// return isValid ? Results.Ok() : Results.Unauthorized();
// });
}
```
## When verification fails
Respond with a non-`2xx` status code (for example `401`) and do not process the payload. The delivery is recorded as `failed` on SpherePay's side, giving you an audit trail in the [Events API](/concepts/webhooks/events-and-replays).
If you registered the same receiver URL under multiple SpherePay applications, each application's endpoint has its own secret. Use the `applicationId` in the payload to select the correct secret — see [Event payloads](/concepts/webhooks/event-payloads#the-applicationid-field). If you cannot parse the body before verifying, try each of your known secrets.
# Authenticate with the SpherePay API
Source: https://docs.spherepay.co/get-started/authentication
All SpherePay API requests use Bearer token authentication. Learn how to generate your API key and pass it correctly in every request.
Every request to the SpherePay API must be authenticated using an API key. SpherePay uses Bearer token authentication — you pass your key in the `Authorization` header of every request. This page covers how to generate a key, how to use it, and how to handle authentication errors.
## Obtain your API key
API keys are generated from the SpherePay dashboard:
1. Log in to [spherepay.co/dashboard](https://spherepay.co/dashboard).
2. Navigate to **Developers → API Keys**.
3. Select **Create New API Key**.
4. Copy the key immediately — it will not be shown again after you leave the page.
Treat your API key like a password. Never expose it in client-side code, commit it to version control, or share it in plaintext. If a key is compromised, revoke it immediately from the dashboard and generate a new one.
## Make an authenticated request
Pass your API key as a Bearer token in the `Authorization` header on every request:
```bash theme={"dark"}
curl https://api.spherepay.co/v2/customer \
-H "Authorization: Bearer YOUR_API_KEY"
```
All requests must use **HTTPS**. Requests made over plain HTTP will be rejected.
## Request headers
| Header | Required | Value |
| - | - | - |
| `Authorization` | Yes | `Bearer YOUR_API_KEY` |
| `Content-Type` | Yes for POST/PATCH | `application/json` |
## Authentication errors
If your API key is missing, malformed, or invalid, the API returns a `403 Forbidden` response:
```json theme={"dark"}
{
"status": 403,
"detail": "Forbidden resource",
"correlationId": "e633f28100526862cadc4c592435eddd"
}
```
If you receive this error on a key you believe is valid, check the following:
* The `Authorization` header is formatted as `Bearer YOUR_API_KEY` with a single space between `Bearer` and the key — no extra characters or quotes.
* The key has not been revoked. Log in to the dashboard under **Developers → API Keys** to verify its status.
* You are sending the request over HTTPS, not HTTP.
# Make Your First SpherePay API Call
Source: https://docs.spherepay.co/get-started/first-api-call
Walk through creating a customer, registering a bank account and wallet, then executing your first on-ramp transfer with the SpherePay API.
This guide walks you through the complete sequence for your first SpherePay integration: create a customer, register a bank account and wallet, then execute an on-ramp transfer. By the end, you'll have issued real API calls against every core resource in the SpherePay model.
All requests go to the base URL: `https://api.spherepay.co/`
## Prerequisites
* You have an API key. If not, see [Authentication](/get-started/authentication).
* You understand the integration model. If not, read [How it works](/get-started/how-it-works) first.
Create an individual customer by sending a `POST` request to `/v2/customer`. Include the customer's contact details and tax identification information.
```bash theme={"dark"}
curl -X POST https://api.spherepay.co/v2/customer \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "individual",
"email": "jane.smith@example.com",
"phone": "+14155550123",
"address": {
"line1": "233 South Wacker Drive",
"line2": "Suite 4700",
"city": "Chicago",
"postalCode": "60606",
"state": "IL",
"country": "USA"
},
"personalInformation": {
"taxIdentificationNumber": "123456789",
"taxIdentificationNumberType": "ssn",
"taxIdentificationNumberCountry": "USA"
}
}'
```
A successful response returns the newly created customer object with a system-generated `id` and a `verificationProfiles` array showing the KYC status as `incomplete`:
```json theme={"dark"}
{
"id": "customer_4fbb292cc6e447da8858e054d1937e7e",
"email": "jane.smith@example.com",
"phone": "+14155550123",
"verificationProfiles": [
{
"name": "kyc_profile_a",
"status": "incomplete",
"criteria": {
"complete": [],
"pending": [],
"required": [],
"errors": []
}
}
],
"createdAt": "2026-03-03T21:25:03.102Z",
"updatedAt": "2026-03-03T21:25:03.102Z",
"type": "individual"
}
```
The customer must complete KYC verification and reach an `approved` status before you can create a transfer on their behalf. Onboarding is a hard prerequisite for all transfers. See the [Individual KYC Flow](/concepts/onboarding/individual-kyc) or [Business KYB Flow](/concepts/onboarding/business-kyb) for next steps.
Once your customer is KYC-approved, register the bank account that will serve as the fiat source for an on-ramp transfer. Send a `POST` request to `/v2/bank-account` with the customer ID and account details.
```bash theme={"dark"}
curl -X POST https://api.spherepay.co/v2/bank-account \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"customerId": "customer_4fbb292cc6e447da8858e054d1937e7e",
"bankName": "Chase",
"accountName": "Jane Doe Checking",
"accountOwner": {
"accountHolderName": "Jane Doe",
"address": {
"line1": "233 South Wacker Drive",
"city": "Chicago",
"state": "IL",
"postalCode": "60606",
"country": "USA"
}
},
"currency": "usd",
"accountDetails": {
"accountNumber": "123456789",
"routingNumber": "021000021",
"accountType": "checking"
},
"networks": ["ach"]
}'
```
A successful response returns the bank account object with a system-generated `id`:
```json theme={"dark"}
{
"id": "bankAccount_7c3d2a1b4e8f4290a1c2d3e4f5a6b7c8",
"customerId": "customer_4fbb292cc6e447da8858e054d1937e7e",
"bankName": "Chase",
"accountName": "Jane Doe Checking",
"currency": "usd",
"networks": ["ach"],
"createdAt": "2026-03-03T21:30:00.000Z",
"updatedAt": "2026-03-03T21:30:00.000Z"
}
```
Register the crypto wallet that will serve as the destination for the on-ramp transfer. Send a `POST` request to `/v2/wallet` with the customer ID and wallet details.
```bash theme={"dark"}
curl -X POST https://api.spherepay.co/v2/wallet \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"customerId": "customer_4fbb292cc6e447da8858e054d1937e7e",
"address": "0xAbC1234567890dEfAB1234567890DEFaB123456",
"network": "ethereum",
"currency": "usdc"
}'
```
A successful response returns the wallet object with a system-generated `id`:
```json theme={"dark"}
{
"id": "wallet_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d",
"customerId": "customer_4fbb292cc6e447da8858e054d1937e7e",
"address": "0xAbC1234567890dEfAB1234567890DEFaB123456",
"network": "ethereum",
"currency": "usdc",
"status": "active",
"createdAt": "2026-03-03T21:35:00.000Z",
"updatedAt": "2026-03-03T21:35:00.000Z"
}
```
With a verified customer, a bank account, and a wallet all registered, you can now create an on-ramp transfer. Specify the bank account as the `source` and the wallet as the `destination`.
```bash theme={"dark"}
curl -X POST https://api.spherepay.co/v2/transfer \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": "100.00",
"customer": "customer_4fbb292cc6e447da8858e054d1937e7e",
"source": {
"type": "bank_account",
"id": "bankAccount_7c3d2a1b4e8f4290a1c2d3e4f5a6b7c8",
"currency": "usd",
"network": "ach"
},
"destination": {
"type": "wallet",
"id": "wallet_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d",
"currency": "usdc",
"network": "ethereum"
}
}'
```
A successful response returns the transfer object. The initial `status` will be `pendingFunding` while SpherePay waits for funds:
```json theme={"dark"}
{
"id": "payout_9z8y7x6w5v4u3t2s1r0q",
"type": "on_ramp",
"status": "pendingFunding",
"customer": "customer_4fbb292cc6e447da8858e054d1937e7e"
}
```
Poll the transfer status by sending a `GET` request to `/v2/transfer/{id}` using the `id` returned in the previous step.
```bash theme={"dark"}
curl https://api.spherepay.co/v2/transfer/payout_9z8y7x6w5v4u3t2s1r0q \
-H "Authorization: Bearer YOUR_API_KEY"
```
The response returns the transfer object with an updated `status` field. A transfer moves through `pendingFunding` → `fundsReceived` → `processing` → `succeeded` (or `failed` if an error occurs). See [Transfer Lifecycle](/concepts/transfers/lifecycle) for all possible statuses and their meanings.
# How SpherePay Works: Integration Overview
Source: https://docs.spherepay.co/get-started/how-it-works
Learn the three-step integration model behind SpherePay: create a verified customer, register a transfer instrument, then execute a transfer.
Understanding the SpherePay integration model before writing any code will save you significant time. Every integration — whether you're building on-ramp, off-ramp, or embedding the ramp widget — follows the same core sequence: authenticate, onboard a customer, register a transfer instrument, and execute a transfer. This page explains each step and the key concepts behind them.
## Integration sequence
Generate an API key from your SpherePay dashboard. Every API request must include this key as a Bearer token in the `Authorization` header. See [Authentication](/get-started/authentication) for setup instructions.
A customer represents the end user or business on whose behalf you are moving money. Before any transfer can be initiated, the customer must pass identity verification — KYC for individuals, KYB for businesses. No transfer can be created without a verified customer record.
Once your customer is verified, link the account or wallet that will serve as the source or destination of the transfer. For on-ramp transfers, you link a bank account as the fiat source and a wallet as the crypto destination. For off-ramp, the direction is reversed.
With a verified customer and at least one bank account and one wallet registered, call `POST /v2/transfer`. SpherePay handles routing, FX conversion, and settlement from that point forward.
Each step is a hard prerequisite for the next. You cannot initiate a transfer without a verified customer, and you cannot complete a transfer without a registered source and destination instrument.
## Step 1 — Create and verify a customer
A customer in SpherePay represents an end user or business on whose behalf you are moving money. Before a customer can transact, they must pass identity verification:
* **Individual customers** go through a KYC (Know Your Customer) flow. This involves identity document submission and a face liveness check.
* **Business customers** go through a KYB (Know Your Business) flow. This involves business document submission and verification of a director or authorized representative.
Verification is handled through Sumsub, either directly via the SpherePay API or through a hosted KYC/KYB link you send to your customer. Once Sumsub approves the application, SpherePay processes the result and updates the customer's status.
See [Customers & Onboarding](/concepts/onboarding/overview) for full integration guides.
## Step 2 — Register a transfer instrument
Once a customer is verified, register the transfer instrument that will serve as the source or destination of a transfer:
* **Bank accounts** — used as the fiat source for on-ramp or the fiat destination for off-ramp.
* **Wallets** — used as the crypto destination for on-ramp or the crypto source for off-ramp.
A single customer can have multiple bank accounts and wallets registered. You specify which instruments to use when creating each transfer.
## Step 3 — Execute a transfer
With a verified customer and the appropriate instruments in place, call `POST /v2/transfer` to initiate the money movement. You specify the customer, the source instrument, the destination instrument, and the amount. SpherePay handles everything else.
See [Transfers](/concepts/transfers/overview) for full details on transfer types, statuses, and supported rails.
## Key concepts
| Concept | Description |
| - | - |
| **Customer** | An individual or business on whose behalf transfers are executed. Must be KYC/KYB verified before any transfer can be initiated. |
| **Bank Account** | A fiat bank account linked to a customer, used for sending or receiving fiat funds. |
| **Wallet** | A crypto wallet address linked to a customer, used for sending or receiving stablecoins. |
| **Transfer** | A single money movement event between a customer's bank account and wallet, in either direction. |
## What SpherePay handles for you
Once you submit a transfer, SpherePay manages the full lifecycle:
* Identity verification orchestration via Sumsub
* Compliance screening and sanctions checks
* Fiat-to-crypto and crypto-to-fiat conversion
* Settlement and payout via the underlying banking and blockchain rails
SpherePay does not maintain a separate sandbox environment. Testing happens directly against production systems using real identity information. This ensures your integration is validated against the same identity verification and banking partnerships your customers will use.
# SpherePay OpenAPI 3.0 Specification Download
Source: https://docs.spherepay.co/get-started/openapi-spec
Download the SpherePay OpenAPI 3.0 specification to generate API clients, import into Postman or Insomnia, or build custom tooling and integrations.
The SpherePay API is documented as an OpenAPI 3.0 specification. The spec is the source of truth for every endpoint, request schema, response schema, and error code — drop it into your favorite tool to generate clients, import collections, or feed it to your own internal services.
spherepay.yaml — OpenAPI 3.0, \~470 KB. The file is served directly from the docs site and reflects the latest published API surface.
## What's in the spec
* Every public endpoint under `/v2/` — customers, transfers, bank accounts, wallets, virtual accounts (Onramper Accounts), offloader wallets, business representatives, documents, enhanced-due-diligence.
* Full request and response schemas for each endpoint, including discriminated unions for currency-specific bank-account shapes (USD vs. EUR vs. BRL).
* Standard error envelopes and status-code mappings.
* Examples for the most common request bodies.
## Common uses
Feed the YAML into [openapi-generator](https://github.com/OpenAPITools/openapi-generator) or [Speakeasy](https://www.speakeasy.com/) to produce a typed SDK in TypeScript, Python, Go, Java, Ruby, and others.
Postman → **Import** → drop the file. You'll get a collection seeded with every endpoint, ready to authenticate with your API key.
Use [Spectral](https://stoplight.io/open-source/spectral) or any OpenAPI-aware linter to validate your application's request payloads against the spec before they hit production.
Tools like [Prism](https://stoplight.io/open-source/prism) spin up a local mock server from the spec, so you can develop against SpherePay's contract without burning sandbox traffic.
## Versioning
The spec tracks the **live `/v2/` API**. Breaking changes are announced in [Release notes](/changelog) before the spec changes; deprecated fields are kept in the spec with `deprecated: true` until removal.
If you need to pin against a specific historical version of the spec, check Git history on this file in the docs repository.
## Issues with the spec
If a field, type, or example doesn't match what you see when calling the API, that's a bug — reach out at [support@spherepay.co](mailto:support@spherepay.co) with the endpoint and a request/response sample and we'll fix the spec.
# First-Party Flows
Source: https://docs.spherepay.co/implementation-guides/first-party-flows
Post-discovery implementation paths when the same legal entity owns both sides of the movement — one-off transfers, Onramper Accounts, and Offloader Wallets.
**When Sphere routes you here:** Discovery confirmed the same legal entity owns both the source and destination — for example, treasury on-ramp, treasury off-ramp, or reusable funding/cash-out for that entity's own bank account and wallet.
This page scopes the **first-party implementation pattern** to build. For object definitions and API payloads, follow the links into [Concepts](/concepts/overview) and [API Reference](/api-reference/customer/post).
Paying employees, suppliers, marketplace sellers, or other third parties? Use [Third-Party Flows](/implementation-guides/third-party-flows) instead.
## Scoping output
By the end of discovery, you and SpherePay should agree on:
* Customer type — individual or business
* Legal entity that owns **both** source and destination
* First motion — on-ramp, off-ramp, or both
* Pattern — one-off transfer, Onramper Account, or Offloader Wallet
* Fiat currency and rail
* Stablecoin and network
* One-off transfers vs reusable instructions
* Movement/conversion only vs fiat balance holding
* Expected monthly volume, average and maximum transaction size
* Verification profile and corridor enablement requirements
* Reconciliation fields your finance team needs
## Choose your pattern
Explicit API control per on-ramp or off-ramp. Best when each movement is initiated individually.
Reusable fiat deposit instructions that auto-convert to stablecoin in the customer's wallet.
Reusable stablecoin deposit address that auto-converts to fiat in the customer's bank account.
| Pattern | Discovery signals | Primary objects |
| - | - | - |
| [One-off transfer](#pattern-one-off-transfer) | Per-movement API control; ad-hoc conversions; per-transfer status tracking | Customer, [wallet](/concepts/transfers/wallets), [bank account](/concepts/transfers/bank-accounts), [transfer](/concepts/transfers/overview) |
| [Onramper Account](#pattern-onramper-account) | Reusable fiat deposits; auto-convert to stablecoin; no transfer call per deposit | Customer, wallet, [Onramper Account](/concepts/automation/onramper-accounts) |
| [Offloader Wallet](#pattern-offloader-wallet) | Reusable stablecoin deposits; auto-convert to fiat; no transfer call per deposit | Customer, bank account, [Offloader Wallet](/concepts/automation/offloader-wallets) |
## How the flow works
Customer bank account or reusable funding account → SpherePay → Customer wallet
```mermaid theme={"dark"}
flowchart LR
BA["Customer
bank account"] -->|Fiat deposit| SP["SpherePay"]
SP -->|Stablecoin| W["Customer
wallet"]
```
Customer wallet → SpherePay → Customer bank account
```mermaid theme={"dark"}
flowchart LR
W["Customer
wallet"] -->|Stablecoin| SP["SpherePay"]
SP -->|Fiat payout| BA["Customer
bank account"]
```
For a treasury round-trip example, see [Treasury management](/solutions/treasury-management).
## Pattern: One-off transfer
**Use when:** The customer initiates each on-ramp or off-ramp individually and needs explicit per-transfer control.
**Discovery signals:**
* Each movement is initiated at a point in time — not triggered by a standing deposit address
* Integrator owns retry, idempotency, and status UX per transfer
* Customer may need to quote or execute transfers one at a time
Register the legal entity via [POST /v2/customer](/api-reference/customer/post).
Finish [individual KYC](/concepts/onboarding/individual-kyc) or [business KYB](/concepts/onboarding/business-kyb) until the [verification profile](/concepts/onboarding/verification-profile) reaches `approved`.
Link the customer's [wallet](/api-reference/wallet/post) and [bank account](/api-reference/bank-account/post) on the same customer record.
If the customer needs a guaranteed rate, create a rate-locked quote via [POST /v2/quote](/api-reference/quote/post) (valid for 30, 60, or 300 seconds, redeemable once) and reference its `quoteId` when creating the transfer.
Call [POST /v2/transfer](/api-reference/transfer/post) with customer, source, and destination.
Poll [GET /v2/transfer/](/api-reference/transfer/get-id) until a terminal status — see [Transfer lifecycle](/concepts/transfers/lifecycle).
Confirm source and destination instruments belong to the **same** onboarded customer. Some corridors require provider-specific enablement beyond standard KYC/KYB — confirm access with SpherePay before building.
**Related Solution:** [Treasury management](/solutions/treasury-management)
## Pattern: Onramper Account
**Use when:** The customer needs reusable fiat deposit instructions that automatically convert to stablecoin in a wallet they own.
**Discovery signals:**
* Customer will send fiat to the same deposit instructions repeatedly
* No `POST /v2/transfer` call should fire on each deposit
* Destination is a stablecoin wallet owned by the same legal entity
Register the legal entity via [POST /v2/customer](/api-reference/customer/post).
Reach `approved` on the required verification profile.
The customer must already own the on-chain address. Register via [POST /v2/wallet](/api-reference/wallet/post).
Call [POST /v2/virtual-account](/api-reference/virtual-account/post). See [Onramper Accounts](/concepts/automation/onramper-accounts).
Provide `depositInstructions` from the response. SpherePay converts incoming fiat and delivers stablecoin automatically.
Poll [GET /v2/transfer](/api-reference/transfer/get) filtered by customer (use `type=microdeposit` for verification deposits) to list transfers processed through the account. Each fiat deposit appears as a transfer once detected — reconcile against your expected deposits. There is no per-deposit API call to anchor status on, so design your polling cadence and reconciliation keys before launch.
Onramper Accounts are an add-on product. Create **one account per customer** — shared accounts break deposit attribution. Confirm the product is enabled for your rail and region.
**Related Solutions:** [Payment acceptance](/solutions/payment-acceptance), [Treasury management](/solutions/treasury-management)
## Pattern: Offloader Wallet
**Use when:** The customer needs a reusable on-chain address that automatically converts incoming stablecoin to fiat in a bank account they own.
**Discovery signals:**
* Customer will send stablecoin to the same address repeatedly
* No `POST /v2/transfer` call should fire on each deposit
* Fiat destination is a bank account owned by the same legal entity
Register the legal entity via [POST /v2/customer](/api-reference/customer/post).
Reach `approved` on the required verification profile.
Link the customer's existing account via [POST /v2/bank-account](/api-reference/bank-account/post).
Call [POST /v2/offloader-wallet](/api-reference/offloader-wallet/post). See [Offloader Wallets](/concepts/automation/offloader-wallets).
Provide the on-chain address from the response. SpherePay converts stablecoin and pays out fiat automatically.
Poll [GET /v2/transfer](/api-reference/transfer/get) filtered by customer to track stablecoin deposits and the resulting fiat payouts through to a terminal status — see [Transfer lifecycle](/concepts/transfers/lifecycle).
Offloader Wallets are an add-on product. Create **one wallet per customer**. To change source/destination currency or rail type, create a new wallet — other fields may be PATCHable via [Update Offloader Wallet](/api-reference/offloader-wallet/patch-id). Verify the bank account holder's legal name and physical address (no PO boxes) before creating the wallet — partners reject PO box addresses, and correction paths for an attached bank account are limited.
**Related Solution:** [Treasury management](/solutions/treasury-management)
## What to send Sphere before kickoff
* Customer type: individual or business
* Legal entity owning source and destination
* First flow: on-ramp, off-ramp, or both
* Selected pattern: one-off transfer, Onramper Account, or Offloader Wallet
* Fiat currency and rail
* Stablecoin and network
* Expected monthly volume
* Average and maximum transaction size
* One-off vs reusable instructions
* Movement/conversion only vs fiat balance holding
* Reconciliation requirements
## Validate before engineering starts
| Blocker | What it usually means |
| - | - |
| Verification profile not approved | Required KYC/KYB profile is incomplete, pending, missing, or rejected |
| Provider/corridor not enabled | Customer may be approved generally but not enabled for the specific corridor or rail |
| Unsupported rail, currency, or network | Combination not available for your product — [Supported rails](/concepts/transfers/supported-rails) |
| Ownership mismatch | Source or destination instrument does not belong to the onboarded customer |
| Wallet confusion | [POST /v2/wallet](/api-reference/wallet/post) registers an existing address — SpherePay does not generate or custody private keys |
| Onramper Account confusion | Automates fiat-to-stablecoin conversion via deposit instructions — confirm movement-only vs balance-holding requirements |
| Offloader Wallet updates | Changing source/destination currency or rail type requires a new wallet; other fields may be PATCHable |
## Pre-kickoff checklist
* [ ] Same legal entity owns source and destination
* [ ] Pattern selected and confirmed with SpherePay
* [ ] Customer created or retrieved; verification profile `approved` for target corridor
* [ ] Provider/corridor enabled if required
* [ ] Wallet and/or bank account registered on the correct customer
* [ ] Rail, currency, and network confirmed
* [ ] Reconciliation and status mapping defined
* [ ] Kickoff inputs sent to SpherePay
## Before go-live
SpherePay has no separate sandbox — integration testing runs against production with real identity data. Run a small-dollar live transfer on each corridor before launch.
The first transfer to a new beneficiary may trigger a compliance RFI from the banking partner. Set this expectation with your customer up front, and make sure your designated RFI owner is ready to respond.
For end-customer settlement questions, share the SpherePay Knowledge Base articles [When will the funds land in my account?](https://spherepay-knowledge-base.help.usepylon.com/articles/9934878291-when-will-the-funds-land-in-my-account) and [My funds are missing or have not landed in my account](https://spherepay-knowledge-base.help.usepylon.com/articles/9884755317-my-funds-are-missing-or-have-not-landed-in-my-account-what-should-i-do) so users can self-serve instead of opening support tickets with you.
## Go deeper
First-party treasury outcome — round-trip on-ramp/off-ramp examples.
One-off transfer lifecycle, BRL/PIX constraints, and status reference.
When source and destination are not the same legal entity.
KYC, KYB, and verification profile requirements.
End-customer support articles — settlement times, missing funds, minimums, and dashboard guides — to hand to your users.
# Implementation Guides
Source: https://docs.spherepay.co/implementation-guides/overview
Post-discovery routing for SpherePay integrations — choose the implementation path, pattern, and docs to use after your funds flow is scoped.
Use Implementation Guides **after discovery**, when Sphere and your team already understand the intended funds flow, parties, rails, and operational model. These pages turn that conversation into a **scoped build path** for your integrator — not a product overview.
New to SpherePay? Start with [How it works](/get-started/how-it-works) and [Concepts](/concepts/overview). Know your business outcome but not your flow type? Read this page first, then [Solutions](/solutions/overview).
## How to use this section
Work through [Before you pick a guide](#before-you-pick-a-guide) with your SpherePay representative — parties, rails, volume, and transfer model.
Use [Route by discovery outcome](#route-by-discovery-outcome) to pick [First-Party](/implementation-guides/first-party-flows) or [Third-Party Flows](/implementation-guides/third-party-flows) and the pattern inside that guide.
Complete the guide's scoping output, kickoff list, and blocker checks before writing integration code.
Follow links into [Concepts](/concepts/overview), [Solutions](/solutions/overview), and [API Reference](/api-reference/customer/post) for object definitions, business examples, and payloads.
## What these guides are for
| Section | Use it to… |
| - | - |
| **Implementation Guides** (this section) | Route to a build path, validate scoping, and know what to send Sphere before kickoff |
| [Concepts](/concepts/overview) | Learn what each object means — customers, transfers, automation, rails |
| [Solutions](/solutions/overview) | See how primitives combine for a business outcome — treasury, payroll, trading |
| [API Reference](/api-reference/customer/post) | Implement request/response details for a specific endpoint |
## Before you pick a guide
Confirm these inputs with your SpherePay representative during or immediately after discovery:
* **Who owns the funds** at each step
* **Customer of record** — who SpherePay must onboard and verify
* **Sender** and **beneficiary** — may differ from the customer of record
* **Platform role** — whether your platform is in the flow of funds
* **Downstream users** — businesses, consumers, or both
* **Fiat currency and rail** — see [Supported rails](/concepts/transfers/supported-rails)
* **Stablecoin and network** — see [Supported assets](/concepts/reference/supported-assets)
* **Transfer model** — one-off API transfers vs reusable deposit/cash-out instructions
* **Balance model** — movement/conversion only vs fiat balance holding
* **Volume** — expected monthly volume, average and maximum transaction size
## Route by discovery outcome
| After discovery, you know… | Start here | Pattern |
| - | - | - |
| The same legal entity owns both the source and destination | [First-Party Flows](/implementation-guides/first-party-flows) | — |
| Each on-ramp/off-ramp is initiated explicitly via API | [First-Party Flows](/implementation-guides/first-party-flows) | [One-off transfer](/implementation-guides/first-party-flows#pattern-one-off-transfer) |
| Reusable fiat deposit instructions into the customer's own wallet | [First-Party Flows](/implementation-guides/first-party-flows) | [Onramper Account](/implementation-guides/first-party-flows#pattern-onramper-account) |
| Reusable stablecoin-to-fiat cash-out to the customer's own bank account | [First-Party Flows](/implementation-guides/first-party-flows) | [Offloader Wallet](/implementation-guides/first-party-flows#pattern-offloader-wallet) |
| A business pays a supplier, vendor, contractor, or other third party | [Third-Party Flows](/implementation-guides/third-party-flows) | [Business pays supplier](/implementation-guides/third-party-flows#pattern-business-pays-supplier) |
| A platform enables its business customers to move funds | [Third-Party Flows](/implementation-guides/third-party-flows) | [Platform serves business customers](/implementation-guides/third-party-flows#pattern-platform-serves-business-customers) |
| A bank or fintech embeds ramps for many end users | [Third-Party Flows](/implementation-guides/third-party-flows) | [Bank or fintech serves consumers](/implementation-guides/third-party-flows#pattern-bank-or-fintech-serves-consumers) |
## Pick a guide
Same legal entity owns source and destination — one-off transfers, Onramper Accounts, and Offloader Wallets.
Different parties or platform-mediated flows — supplier payouts, platform B2B, and consumer embed paths.
## First-party or third-party?
If the routing table above does not match cleanly, use this fork:
| Flow type | Route when | Example |
| - | - | - |
| **First-party** | The same legal entity owns the source and destination | Company bank account → same company's wallet |
| **Third-party** | Sender, beneficiary, or customer of record are different entities, or a platform serves downstream users | Company wallet → supplier bank account |
Still unsure? Ask: **Does the same legal entity own both the source and destination?** If yes → first-party. If no → third-party.
## Map to Solutions
Implementation Guides define **how to build**. Solutions define **what business outcome** you are shipping. Use both:
First-party treasury on-ramp/off-ramp — pair with [First-Party Flows](/implementation-guides/first-party-flows).
Platform fiat acceptance — often [Third-Party Flows](/implementation-guides/third-party-flows) or Onramper pattern.
Third-party disbursements — pair with [Third-Party Flows](/implementation-guides/third-party-flows).
Supplier and invoice settlement — pair with [Third-Party Flows](/implementation-guides/third-party-flows).
# Third-Party Flows
Source: https://docs.spherepay.co/implementation-guides/third-party-flows
Post-discovery implementation paths when funds move to or from another legal entity, or when a platform serves downstream customers.
**When Sphere routes you here:** Discovery confirmed the sender, customer of record, and beneficiary are not all the same legal entity — or your platform enables downstream customers to move funds.
Third-party flows require **more scoping** than first-party: parties, purpose-of-payment, documents, corridor availability, beneficiary setup, and the correct product endpoint path. This page defines what to validate before engineering starts. It does not replace [Solutions](/solutions/overview) or the [API Reference](/api-reference/customer/post).
Third-party payment availability depends on geography, rail, customer profile, and compliance approval. Third-party payments are **not available in Texas or Pennsylvania**. Confirm supported corridors and regions with SpherePay before building.
Moving funds between a customer's **own** bank account and **own** wallet? Use [First-Party Flows](/implementation-guides/first-party-flows) instead.
## Scoping output
By the end of discovery, you and SpherePay should agree on:
* Platform legal entity (if applicable)
* **Customer of record**
* **Sender** and **beneficiary**
* **Funds owner** at each step
* Whether the **platform is in the flow of funds**
* Downstream user type — business, consumer, or both
* Implementation pattern — supplier payout, platform B2B, or consumer embed
* Payment purpose and reason for payment
* Supporting documents and beneficiary relationship context, if required
* Required sender/recipient visibility on the wire or statement
* Fiat currency and rail; stablecoin and network if applicable
* Expected monthly volume, average and maximum transaction size
* **RFI and customer support owner**
* Approved **endpoint and product path** for payment creation and status polling
## Choose your pattern
Pay a supplier, vendor, contractor, or exporter — purpose-of-payment and documents may apply.
Fintech or platform enabling downstream business customers to move funds.
Embedded ramps for individual end users — scope KYC, fraud, and pilot first.
| Pattern | Discovery signals | Primary scoping focus |
| - | - | - |
| [Business pays supplier](#pattern-business-pays-supplier) | Different legal entity receives funds; invoice/vendor payment | Beneficiary setup, purpose, documents |
| [Platform serves business customers](#pattern-platform-serves-business-customers) | Platform + downstream businesses; customer ID mapping | Customer of record, onboarding scope |
| [Bank or fintech serves consumers](#pattern-bank-or-fintech-serves-consumers) | Individual end users; embedded UX | User classification, compliance model |
## Pattern: Business pays supplier
**Use when:** An onboarded business pays a supplier, vendor, contractor, exporter, or other third-party beneficiary.
**Discovery signals:**
* Recipient is **not** the same legal entity as the sender
* Payment tied to an invoice, purchase order, contract, or service relationship
* Purpose-of-payment, memo, or supporting documents may be required
* Cross-border settlement may apply — see [Cross-border trade finance](/solutions/cross-border-trade-finance)
**Parties to confirm:**
| Party | Scoping question |
| - | - |
| Customer of record | Who is onboarded with SpherePay — payer, platform, or other? |
| Sender | Who initiates and funds the payment? |
| Beneficiary | Who receives funds — and how must they be registered or reviewed? |
| Funds owner | Whose money is moving at each step? |
Register the payer via [POST /v2/customer](/api-reference/customer/post) and complete [business KYB](/concepts/onboarding/business-kyb) or [individual KYC](/concepts/onboarding/individual-kyc) as applicable.
Link the payer's [bank account](/api-reference/bank-account/post) or [wallet](/api-reference/wallet/post).
Depending on the flow, the beneficiary may need to be onboarded as a SpherePay customer or otherwise reviewed/registered before funds can be sent. Confirm with SpherePay before implementation.
Gather payment purpose, reason for payment, relationship context, and supporting documents — see [Additional requirements](#additional-requirements-for-third-party-payments). For **SWIFT** payouts a supporting document is required — see [Supporting documents for SWIFT third-party payments](#supporting-documents-for-swift-third-party-payments).
Use the **supported endpoint for your approved third-party flow** — confirm the path with SpherePay before engineering starts.
Poll status using the retrieval path confirmed for your flow. Reconcile against invoice or purchase-order records.
Third-party payments are not "first-party transfer with a different recipient." Endpoint path, beneficiary model, compliance fields, and status handling can all differ. Do not assume [POST /v2/transfer](/api-reference/transfer/post) applies.
## Pattern: Platform serves business customers
**Use when:** A fintech or platform enables its business customers to move funds through SpherePay.
**Discovery signals:**
* Your customer is a **platform**, not only an end payer
* Downstream businesses send or receive funds through your product
* Internal customer IDs must map to SpherePay customer IDs
* RFI and support ownership must be defined before go-live
Decide whether the platform entity, each downstream business, or both must be onboarded — confirm with SpherePay.
If using [Platform-Managed onboarding](/concepts/onboarding/overview#onboarding-models), confirm eligibility with Sphere Compliance before building.
Use [POST /v2/customer](/api-reference/customer/post) for each entity that must be verified. Map internal IDs to SpherePay customer IDs.
Register [bank accounts](/concepts/transfers/bank-accounts) and [wallets](/concepts/transfers/wallets) on the correct customer records.
Confirm endpoint path for payment creation and status polling. For fiat-acceptance outcomes, see [Payment acceptance](/solutions/payment-acceptance).
Do not assume onboarding only the platform is enough. Depending on the flow, each underlying business may also need to be onboarded and verified before it can send or receive funds.
## Pattern: Bank or fintech serves consumers
**Use when:** A regulated institution or fintech exposes ramps to individual end users at scale.
**Discovery signals:**
* Downstream users include **consumers** (not only businesses)
* Embedded on-ramp/off-ramp or hosted UX is required
* KYC ownership, fraud controls, and chargeback considerations differ from B2B
* A limited pilot on one corridor and user segment is appropriate before broad rollout
Separate business users from consumer users — onboarding paths and partner requirements differ.
Define whether SpherePay, your platform, or a hybrid model handles KYC/KYB, RFIs, and support.
Document which fields you collect upstream vs. which SpherePay collects via API or [hosted link](/concepts/onboarding/kyc-via-link).
API integration vs [Ramp Widget](/widget/overview) — based on who owns the frontend experience.
Validate one corridor, one rail, and one user type before scaling volume or user segments.
Consumer flows may require different onboarding, fraud, and partner considerations than B2B. Confirm eligibility with SpherePay before assuming a business third-party pattern scales to consumers.
## Additional requirements for third-party payments
Plan to collect and pass the following when your approved corridor requires them:
* **Payment purpose** — classification for compliance and bank partners
* **Reason for payment** — plain-language description of why funds are moving
* **Payment purpose code** — allowed values vary by corridor; confirm the set with SpherePay before building
* **Supporting documents** — contracts, invoices, or relationship evidence when required
* **Beneficiary relationship information** — supplier, contractor, marketplace seller, etc.
* **Sender/recipient visibility** — what the beneficiary sees on the wire or statement. Named sender visibility (the sender's business name on the receiving statement) is only guaranteed on wire — SpherePay cannot guarantee it on ACH because the receiving institution controls what displays. If the beneficiary requires an identifiable sender name, scope the rail to `network: "wire"`. See [Supported Rails](/concepts/transfers/supported-rails).
* **Review before settlement** — some flows require document or manual review; plan status UX accordingly
Use only fields and statuses documented in the public API for your approved product path.
## Supporting documents for SWIFT third-party payments
SWIFT payouts (USD delivered to an international bank account) are frequently **third-party payments** — the beneficiary is a different legal entity than your onboarded customer. A third-party SWIFT payment must carry a **supporting document** (typically the invoice it settles) along with a payment reason and description. The payment is rejected if any of these are missing.
SWIFT requires **verification profile C** — a USD SWIFT bank account cannot be created until profile C is approved. Register the beneficiary as a USD SWIFT bank account (`currency: "usd"`, `networks: ["swift"]`) with an `accountNumber` (IBAN or BBAN) and `bic`, plus up to three optional `intermediaryBics`. Confirm profile C is enabled with SpherePay before building.
### Eligible documents
Upload the single document that best evidences the payment — most commonly the **invoice** it settles. Other acceptable evidence includes purchase orders, signed contracts, and service agreements. Accepted formats are **PDF, JPEG, and PNG**, up to **10 MB**.
### How it works
Call [POST /v2/document](/api-reference/document/post) with `target: "transfer"` and attach the file. No `documentType` is needed for this target. The response returns the document's `id` (for example, `document_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6`).
Create the SWIFT transfer with `isThirdParty: true` and include `documentId` (the `id` from step 1), `paymentReason`, and `paymentDescription`. For a third-party payment **all three are required** — the request is rejected with a `422` if any are missing, and the document must belong to your application.
Poll status as usual and reconcile against the invoice or purchase-order the document represents.
**Reuse the same document across payments.** A `documentId` is not consumed when you reference it — you can pass the same one on multiple transfers. This is useful for **recurring payments against a single contract or purchase order**: upload the supporting document once, then reference its `documentId` on each payment in the series instead of re-uploading it every time.
`paymentReason` accepts one of: `personal`, `investment`, `real_estate`, `tax`, `loan`, `bills`, `reimbursement`, `professional_services`, `family_support`, `education`, `rent`, `donation`, `gift`, `insurance`, `medical`, `savings`, `travel`, `mortgage`, `fine`, `dividend`, `agriculture`, `import_export`, `art`, or `other`.
### What to put in the document description
When you upload the document, use the optional `description` field to give SpherePay **context it can use to review and route the payment faster** — not just a restatement of the file name. Include, where relevant:
* What the document is — e.g., "Commercial invoice INV-2024-0098"
* The purpose of the payment — e.g., "Payment for Q2 manufacturing services"
* The parties and their relationship — e.g., "From Acme Inc. to Shenzhen Supplier Co., contracted manufacturer"
* The invoice or PO number the payment settles, and the amount if it differs from the full invoice total
Clear, specific descriptions reduce the chance of a compliance RFI and speed up review. The document `description` and the payment's `paymentDescription` (1–500 characters) are separate fields — use both to give full context.
## Confirm before building
Confirm customer of record, sender, beneficiary, and funds owner at each step.
Determine whether the platform is in the flow of funds and whether downstream users are businesses, consumers, or both.
Confirm rail, currency, and region availability — including TX/PA restrictions for third-party payments.
Define payment purpose, required documents, beneficiary setup model, and sender/recipient visibility.
Verify the approved path for payment creation and status polling with SpherePay — see [Payment creation and status](#payment-creation-and-status).
Define who handles RFIs and customer support before go-live.
## Payment creation and status
Third-party payment paths are **product-specific**. The public [API Reference](/api-reference/transfer/post) documents `POST /v2/transfer` for on-ramp/off-ramp and quoted transfer flows. **Third-party B2B payment creation may use a different endpoint path** — confirm with SpherePay before implementation.
| Flow type | Guidance |
| - | - |
| First-party on-ramp/off-ramp | [POST /v2/transfer](/api-reference/transfer/post) — see [First-Party Flows](/implementation-guides/first-party-flows) |
| Quoted fiat-to-fiat corridor (when enabled) | [POST /v2/quote](/api-reference/quote/post) then transfer creation with `quoteId` — confirm applicability |
| Approved third-party B2B payments | Use the endpoint path confirmed by SpherePay — do not assume first-party v2 transfer |
Poll status using the retrieval path confirmed for your approved flow. Third-party responses may include fields and statuses beyond the [first-party transfer lifecycle](/concepts/transfers/lifecycle) — confirm the full set with SpherePay.
## What to send Sphere before kickoff
* Platform legal entity (if applicable)
* Customer of record
* Sender and beneficiary
* Funds ownership at each step
* Whether the platform is in the flow of funds
* Downstream user type: business, consumer, or both
* Selected pattern: supplier payout, platform B2B, or consumer embed
* Payment purpose code and reason for payment
* Supporting documentation type, if required
* Beneficiary relationship / recipient context
* Required sender/recipient visibility
* Fiat currency and rail; stablecoin and network if applicable
* Expected monthly volume
* Average and maximum transaction size
* RFI/support owner
* Related Solution guide, if identified
## Validate before engineering starts
| Blocker | Why it matters |
| - | - |
| Verification profile not approved | Required KYC/KYB profile is incomplete, pending, missing, or rejected |
| Provider/corridor not enabled | Customer approved generally but not enabled for the specific corridor or rail |
| Geography / corridor restriction | Third-party payments are not available in **Texas or Pennsylvania**; other regions may have limits |
| Customer of record unclear | SpherePay cannot determine who must be onboarded |
| Platform in the flow of funds | May change onboarding, compliance, and partner requirements |
| Beneficiary unidentified | Payout cannot be evaluated or routed |
| Missing or unsupported payment purpose | Required purpose code or reason missing or not enabled for corridor |
| Missing supporting documents | Settlement may block until documents are provided |
| Third-party flow using first-party assumptions | Treasury/on-ramp patterns may not apply to supplier or platform payouts |
| Wrong endpoint assumed | Payment creation path must match your approved third-party product |
| Consumer scale with B2B pattern | Consumer embed may require a different path |
## Pre-kickoff checklist
* [ ] Customer of record, sender, and beneficiary identified
* [ ] Funds ownership and platform role documented
* [ ] Pattern selected and confirmed with SpherePay
* [ ] Required parties onboarded or beneficiary setup confirmed for corridor
* [ ] Provider/corridor and geography validated (including TX/PA)
* [ ] Payment purpose, reason, and documents collected if required
* [ ] For SWIFT: verification profile C approved, USD SWIFT beneficiary account registered, and (for third-party) `paymentReason`, `paymentDescription`, and an uploaded `documentId` ready
* [ ] Endpoint path for create and status confirmed with SpherePay
* [ ] RFI/support owner defined
* [ ] Reconciliation plan defined
* [ ] Kickoff inputs sent to SpherePay
## Before go-live
SpherePay has no separate sandbox — integration testing runs against production with real identity data. Run a small-dollar live transfer on each corridor before launch.
The first transfer to a new beneficiary may trigger a compliance RFI from the banking partner. Set this expectation with your customer up front, and make sure your [RFI and customer support owner](#scoping-output) is ready to respond.
For end-customer settlement questions, share the SpherePay Knowledge Base articles [When will the funds land in my account?](https://spherepay-knowledge-base.help.usepylon.com/articles/9934878291-when-will-the-funds-land-in-my-account) and [My funds are missing or have not landed in my account](https://spherepay-knowledge-base.help.usepylon.com/articles/9884755317-my-funds-are-missing-or-have-not-landed-in-my-account-what-should-i-do) so users can self-serve instead of opening support tickets with you.
## Go deeper
Third-party disbursements to employees and contractors.
Importer–exporter and invoice settlement patterns.
Platform-mediated fiat acceptance with stablecoin settlement.
When source and destination belong to the same legal entity.
End-customer support articles — settlement times, missing funds, minimums, and dashboard guides — to hand to your users.
# SpherePay: Stablecoin payments for global businesses
Source: https://docs.spherepay.co/introduction
SpherePay is an API-first platform for on-ramp and off-ramp transfers. Convert between USD, EUR, BRL and USDC, USDT, EURC with built-in KYC/KYB compliance.
Sphere is modern infrastructure for moving money between fiat and stablecoins. One API, one compliance layer, and coverage across USD, EUR, BRL, USDC, USDT, and EURC — wired to bank rails (ACH, Wire, SEPA, PIX) and stablecoin networks (Solana, Ethereum, Polygon, Base, Avalanche, Arbitrum, Tron).
Use Sphere to on-ramp customers from a US bank account into USDC on Solana, settle a USD payroll run as stablecoin to employee wallets, accept inbound EUR via SEPA and auto-convert to USDC, or run an FX trade across BRL ↔ USD with PIX and ACH as the fiat legs. Same API, same data model, same compliance layer for every flow.
Understand the core integration model: customers, instruments, and transfers.
Generate your API key and make your first authenticated request.
Step-by-step walkthrough to create a customer and execute a transfer.
Full endpoint specs, schemas, and request/response examples.
## Easy as 1, 2, 3....
Call `POST /v2/customer` to register an individual or business, then complete KYC (individual) or KYB (business) verification before initiating any transfer.
Call `POST /v2/bank-account` to link a fiat rail (ACH, Wire, SEPA, PIX) or `POST /v2/wallet` to link a crypto wallet — bound to the verified customer.
Call `POST /v2/transfer` with the customer, source, and destination to initiate an on-ramp or off-ramp.
## What you can build
Outcome-shaped guides — combine Sphere's primitives to ship the use case your customers need without rebuilding compliance, FX, or settlement from scratch.
Pay W-2 employees and 1099 contractors in stablecoins or fiat — with the virtual-account pattern that ships without a per-paycheck API call.
Accept fiat from end users via wire, ACH, SEPA, or PIX — auto-settled as USDC, USDT, or EURC to your wallet or directly to a protocol contract.
Currency-pair trading with stablecoins as the bridge. BRL ↔ USD and USD ↔ EUR walkthroughs, more corridors as rails expand.
Settle invoices and receivables across currencies for importers, exporters, and trade finance platforms.
## Explore by product
Onboard individuals and businesses with API or hosted link flows.
Create and track on-ramp and off-ramp transfers.
Automated virtual bank accounts that convert fiat deposits to stablecoins.
Automated wallets that convert incoming stablecoins to fiat payouts.
# Cross-Border Trade Finance
Source: https://docs.spherepay.co/solutions/cross-border-trade-finance
Settle international invoices and receivables across currencies using stablecoins as the bridge — built for importers, exporters, and trade finance platforms.
Trade finance workflows move money across borders against invoices, purchase orders, or receivables. The traditional rails — international wires, correspondent banks, letters of credit — are slow, opaque, and expensive. Using stablecoins as the settlement layer cuts settlement times from days to minutes and gives both parties full on-chain visibility.
This guide covers the integration pattern for importer–exporter payments, supplier financing, and B2B receivables platforms.
## How it works
The flow is a two-leg sandwich: fiat on one side, fiat on the other, stablecoins as the bridge.
```mermaid theme={"dark"}
flowchart LR
SB["Importer
bank (origin)"] -->|"USD ACH / Wire"| SP["SpherePay
on-ramp"]
SP -->|USDC| BC["Blockchain
(near-instant)"]
BC -->|USDC| LO["Local off-ramp
(destination)"]
LO -->|Local fiat| RB["Exporter
bank (destination)"]
```
SpherePay handles the USD ↔ USDC conversion. Where SpherePay's fiat rails reach (USD, EUR, BRL), the second leg is also a SpherePay off-ramp. For other corridors, you (or your partner) handle the local fiat off-ramp in the destination country.
## Why stablecoin settlement for trade finance
| Traditional correspondent banking | Stablecoin settlement |
| - | - |
| 3–5 business days | Minutes on-chain + local fiat settlement |
| \$25–50 wire fees + FX spread | Transparent on-chain fees + spot FX |
| Opaque FX rates and routing | Public blockchain trail |
| Limited visibility into status | End-to-end tracking |
For trade finance specifically:
* **Receivables platforms** can settle invoices to suppliers in minutes rather than days, shortening DSO.
* **Importers** get predictable FX pricing — the stablecoin leg is on-chain and quotable upfront.
* **Exporters** can elect to hold USDC for treasury flexibility rather than auto-convert to local fiat.
## Coverage today
SpherePay handles end-to-end where both legs land on supported fiat rails:
* ✅ USD ACH and Wire (US)
* ✅ EUR SEPA (EU)
* ✅ BRL PIX (Brazil)
* ✅ Stablecoin transfers between wallets (USDC, USDT, EURC across supported networks)
For corridors outside these rails (e.g. GBP, MXN, INR), you'll need a local partner for the destination off-ramp. SpherePay handles the on-ramp + stablecoin leg; the partner converts USDC to local currency.
## Implementation pattern
### Step 1 — On-ramp (importer side)
Convert the importer's fiat to USDC.
```bash theme={"dark"}
curl -X POST https://api.spherepay.co/v2/transfer \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": "100000.00",
"customer": "customer_importer",
"source": {
"type": "bank_account",
"id": "bankAccount_importer",
"currency": "usd",
"network": "wire"
},
"destination": {
"type": "wallet",
"id": "wallet_settlement",
"currency": "usdc",
"network": "sol"
}
}'
```
### Step 2 — Stablecoin transfer (settlement leg)
Move USDC to the exporter's wallet (or your partner's settlement wallet). This is a standard on-chain transfer — send USDC directly from your settlement wallet using your preferred wallet infrastructure.
### Step 3 — Off-ramp (exporter side)
If the exporter banks in the US, EU, or Brazil, use SpherePay to convert back to local fiat:
```bash theme={"dark"}
curl -X POST https://api.spherepay.co/v2/transfer \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": "100000.00",
"customer": "customer_exporter",
"source": {
"type": "wallet",
"id": "wallet_exporter",
"currency": "usdc",
"network": "sol"
},
"destination": {
"type": "bank_account",
"id": "bankAccount_exporter",
"currency": "eur",
"network": "sepa"
}
}'
```
For other corridors, hand off to your local partner.
## Worked example — receivables platform
For a trade receivables platform settling supplier invoices:
```javascript theme={"dark"}
async function settleInvoice(buyerId, supplierCountry, invoiceAmount) {
// 1. On-ramp buyer USD → USDC
const onRamp = await createTransfer({
amount: invoiceAmount,
customer: buyerId,
source: {
type: "bank_account",
id: buyerBankAccount,
currency: "usd",
network: "wire",
},
destination: {
type: "wallet",
id: settlementWallet,
currency: "usdc",
network: "sol",
},
});
await waitForTransfer(onRamp.id);
// 2. Route USDC to destination
if (supplierCountry === "BR") {
// SpherePay can off-ramp BRL directly
return createTransfer({
amount: invoiceAmount,
customer: supplierId,
source: { type: "wallet", id: settlementWallet, currency: "usdc", network: "polygon" },
destination: { type: "bank_account", id: supplierBrlBank, currency: "brl", network: "pix" },
});
} else {
// Hand off to local partner for unsupported corridors
const partnerWallet = getPartnerWallet(supplierCountry);
await sendOnChain(settlementWallet, partnerWallet, invoiceAmount);
await notifyPartner(supplierCountry, invoiceAmount);
}
}
```
## Compliance requirements
### Originating side
* **KYC/KYB verification** — All originating businesses must complete [KYB](/concepts/onboarding/business-kyb); individuals require [KYC](/concepts/onboarding/individual-kyc).
* **Source of funds documentation** — Required for larger trade payments; collect commercial invoices, POs, or trade contracts.
### Receiving side
* **Local regulations** — Any third-party partner must comply with destination-country money transmission laws.
* **KYC/AML on recipient** — Per local requirements.
* **Cross-border reporting** — Maintain records as required by both jurisdictions.
## Best practices
* **Pick the right network.** Solana and Base offer the lowest fees and fastest finality. Ethereum is better for institutional counterparties already custodying USDC there.
* **Lock FX upfront.** For invoiced amounts, quote and lock the FX rate before initiating the on-ramp so margin is predictable.
* **Build partner redundancy.** Have backup off-ramp partners for each destination country in case primary routes are unavailable.
* **Track end-to-end.** Poll transfer status and pair with blockchain monitoring for full visibility from importer wire to exporter deposit.
* **Reconcile against invoices.** Maintain a mapping between trade documents (invoice number, PO) and transfer IDs.
## Roadmap
SpherePay is actively expanding international rails — track the [Changelog](/changelog) for additional fiat corridors, single-call cross-currency transfers, and direct trade-finance integrations.
## Related
Full transfer creation and tracking reference.
Per-corridor walkthroughs (BRL ↔ USD, USD ↔ EUR) with the Offloader Wallet pattern for one-call settlement.
KYC/KYB requirements for both legs.
Currency, network, and rail compatibility.
# SpherePay Solutions: Use Case Integration Guides
Source: https://docs.spherepay.co/solutions/overview
Outcome-shaped guides for common SpherePay use cases — payroll, payment acceptance, trading, cross-border trade finance, and treasury management.
The Solutions section shows how to combine SpherePay's primitives — customers, transfers, onramper accounts, offloader wallets — to deliver a specific business outcome. Each guide walks through the data model, the API calls in order, and the operational concerns for one shape of customer.
Before choosing a solution, confirm your flow type and implementation pattern with SpherePay during discovery. First-party and third-party flows use different scoping, compliance, and build paths. Start with [Implementation Guides](/implementation-guides/overview).
If you're still learning the platform, read [Concepts](/concepts/overview) first. If you want the API surface, jump to the [API Reference](/api-reference/customer/post).
## Solutions
Pay W-2 employees and 1099 contractors in stablecoins or fiat. Includes the recommended virtual-account pattern that lets you ship without a transfer call per paycheck.
Accept fiat from end users and have it auto-settled as stablecoins on-chain. Covers e-commerce, marketplaces, and DeFi funding flows — built on Onramper Accounts.
Currency-pair trading with stablecoins as the bridge. Includes BRL ↔ USD and USD ↔ EUR walkthroughs, with more corridors as rails expand.
Settle invoices and receivables across currencies for importers, exporters, and trade finance platforms.
On-ramp, off-ramp, and balance tracking for company treasuries holding stablecoins alongside fiat.
# Accept Fiat Payments and Settle in Stablecoins
Source: https://docs.spherepay.co/solutions/payment-acceptance
Accept fiat payments from end users and settle them as on-chain stablecoins automatically, with no per-payment API call. Built on Onramper virtual accounts.
Accept fiat payments from your end users and have them delivered to your wallet as USDC, USDT, or EURC — automatically, without writing a `POST /v2/transfer` call per payment. The primitive is an **[Onramper Account](/concepts/automation/onramper-accounts)**: a dedicated virtual bank account that auto-converts any incoming fiat deposit into stablecoins and settles to a registered wallet.
This pattern is the inverse of [Trading](/solutions/trading) — instead of orchestrating each conversion, you provision the account once and let it run.
## When to use this pattern
* **E-commerce and B2B billing.** Invoice customers in USD, EUR, or BRL — they pay via wire or ACH against a virtual account, and you receive USDC.
* **Marketplaces.** Provision one Onramper Account per seller. Each seller's fiat receipts auto-settle to a seller-controlled wallet.
* **DeFi funding.** Let users fund an on-chain position via wire or card. The user wires fiat to their Onramper Account, USDC settles directly to the protocol's contract or a wallet you sweep into the protocol.
* **Subscription billing.** Recurring ACH pulls from customers land in their Onramper Account and convert without per-billing-cycle API work.
* **Treasury inbound.** Receive customer payments in fiat without holding fiat — convert at the point of receipt.
The common thread: anytime you'd otherwise write a transfer-per-payment loop, an Onramper Account replaces it with a single setup call and zero ongoing transfer API calls.
## How it works
```mermaid theme={"dark"}
flowchart LR
P["End user
(payer)"] -->|"USD/EUR/BRL"| VA["Onramper Account
(virtual bank account)"]
VA -->|"USDC/USDT/EURC"| W["Your wallet
(or protocol contract)"]
```
1. **Provision an Onramper Account** for each payer (or per receiving wallet — depends on your model).
2. **Share the deposit instructions** with your end user — they wire USD, ACH, or SEPA against the virtual account just like any other bank account.
3. **SpherePay auto-converts and settles.** Each incoming deposit is converted to the configured stablecoin and delivered to the registered wallet address — no transfer API call required.
You can fan this out: one Onramper Account per customer for clean reconciliation, one per product line, or one per wallet destination.
## Setup
### Step 1 — Verify the customer
The customer who owns the Onramper Account must complete [KYC](/concepts/onboarding/individual-kyc) or [KYB](/concepts/onboarding/business-kyb) and be approved. The customer can be a single user, a business entity (for a marketplace seller), or your own company (for a single-account inbound funnel).
### Step 2 — Register the destination wallet
The wallet that will receive the stablecoin output of every deposit.
**POST** `https://api.spherepay.co/v2/wallet`
```json theme={"dark"}
{
"customerId": "{{customer_id}}",
"network": "ethereum",
"address": "0xabcd1234...your wallet or contract..."
}
```
Store the returned `id` as `{{wallet_id}}`. For a DeFi flow, this can be the address of the protocol's vault or pool contract directly — Sphere will deliver USDC to it on every conversion.
### Step 3 — Create the Onramper Account
**POST** `https://api.spherepay.co/v2/virtual-account`
```json theme={"dark"}
{
"customerId": "{{customer_id}}",
"destination": {
"walletId": "{{wallet_id}}",
"currency": "usdc",
"network": "ethereum"
},
"source": {
"currency": "usd",
"networks": ["ach", "wire"]
}
}
```
The response includes `accountDetails` — the virtual bank account's routing and account numbers. Share these with your payer.
For EUR-denominated inbound, swap to:
```json theme={"dark"}
"source": {
"currency": "eur",
"networks": ["sepa"]
}
```
For BRL inbound, use `currency: "brl"` and `networks: ["pix"]` (subject to BRL verification-profile requirements — see [Trading](/solutions/trading#prerequisites)).
### Step 4 — Share deposit instructions
From the response, surface the account number, routing number (or IBAN/BIC for SEPA, PIX key for BRL), and account-holder name in your UI or invoice. The payer initiates an ACH or wire to that account.
### Step 5 — Receive
Each deposit triggers:
1. Sphere receives the fiat into the virtual account.
2. Sphere converts to the configured stablecoin at the prevailing rate.
3. The stablecoin is delivered to the registered wallet — `{{wallet_id}}`.
Track activity on the Onramper Account via:
```bash theme={"dark"}
curl "https://api.spherepay.co/v2/transfer?customer={{customer_id}}" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## DeFi application
For DeFi platforms specifically, the Onramper Account pattern unlocks **on-ramp into a protocol position in one user action**:
1. User decides to deposit into your protocol.
2. Your app shows them the deposit instructions for an Onramper Account configured to settle USDC directly to the protocol's vault contract (or an intermediary wallet you sweep into the protocol).
3. User wires USD from their bank.
4. USDC lands at the protocol contract; the deposit reflects in their on-chain position automatically.
The user never touches a wallet, never bridges, never pays gas to onboard. The fiat-to-position flow becomes a single wire.
For protocols requiring more orchestration (e.g. permit signatures, deposit functions with specific calldata), use an intermediary wallet that you control and trigger the protocol interaction on stablecoin arrival.
## Marketplace application
Provision one Onramper Account **per seller** at onboarding:
```javascript theme={"dark"}
async function onboardSeller(sellerCustomerId, sellerWalletId) {
// Each seller gets their own dedicated virtual account
const va = await createVirtualAccount({
customerId: sellerCustomerId,
destination: {
walletId: sellerWalletId,
currency: "usdc",
network: "polygon",
},
source: {
currency: "usd",
networks: ["ach"],
},
});
// Hand the seller their unique deposit details
return {
routing: va.accountDetails.routingNumber,
account: va.accountDetails.accountNumber,
name: va.accountDetails.accountHolderName,
};
}
```
Buyers pay each seller directly via ACH against the seller's virtual account. The marketplace platform never touches the funds — Sphere handles the entire fiat-to-USDC settlement.
## Compliance considerations
* **KYC the account owner.** The customer who owns the Onramper Account must be verified. For marketplaces, this means each seller goes through KYC/KYB.
* **Source of funds.** Large or unusual deposits may require additional documentation per Sphere's compliance policies.
* **Sanctions screening.** Sphere screens deposits at the rail level, but you remain responsible for the legality of your underlying business activity.
* **Tax reporting.** Convert-to-stablecoin events may be taxable in some jurisdictions — coordinate with your tax advisor.
## Best practices
* **One account per receiver.** Don't share an Onramper Account across multiple sellers or use cases — reconciliation gets messy. Dedicated accounts make every deposit unambiguous.
* **Set conservative limits.** Use Sphere's per-account deposit caps to bound exposure on any single account.
* **Poll deposit status.** The Onramper Account doesn't fire webhooks today; poll [GET /v2/transfer](/api-reference/transfer/get) filtered by customer (use `type=microdeposit` for verification deposits) to detect new deposits.
* **Use SEPA Instant where possible** for EUR flows requiring fast settlement.
* **Plan for refunds.** If the underlying business model requires returning funds, you'll need a separate off-ramp flow (see [Offloader Wallets](/concepts/automation/offloader-wallets)).
## Related
The primitive this solution is built on — virtual bank accounts with automatic stablecoin conversion.
The inverse pattern — orchestrated, per-transfer conversion.
KYC/KYB for the account owner.
Which currencies and networks are supported.
# Stablecoin and Fiat Payroll for Global Teams
Source: https://docs.spherepay.co/solutions/payroll
Pay W-2 employees and 1099 contractors in stablecoins or fiat. Cover both crypto-native and traditional payroll models with a single integration.
Use SpherePay to disburse payroll in stablecoins or convert stablecoin payments to fiat for employees and contractors. This is common for companies with globally distributed teams or crypto-native workforces.
## Two payroll models
| Model | Flow | Best for |
| - | - | - |
| **Stablecoin payroll** | USD → USDC → employee wallet | Crypto-native employees, international contractors |
| **Fiat payroll** | USDC → USD → employee bank | Traditional employees who prefer fiat |
## Recommended — virtual accounts for stablecoin payroll
The fastest way to ship a stablecoin payroll integration is to use **[Onramper Accounts](/concepts/automation/onramper-accounts)** (virtual bank accounts) instead of per-paycheck `POST /v2/transfer` calls.
Each employee gets their own dedicated virtual USD bank account (account + routing number, or SEPA details for EUR). On payday you initiate a single ACH/wire from your payroll platform to each employee's virtual account using your existing payroll tooling. SpherePay automatically converts the deposit to USDC and delivers it to the employee's registered wallet — no transfer API call required.
**Why this pattern wins:**
* **Faster integration.** No per-payment API logic, retry handling, or idempotency keys.
* **Reuses your payroll system.** Initiate the ACH from Gusto, Rippling, or your existing rails — the virtual account "looks like" a normal bank account to the originator.
* **Auditable.** Each payment is recorded as a deposit on a dedicated account, mapped 1:1 to an employee.
* **Hands-off after setup.** Onboarding the employee + provisioning the Onramper Account is the only API work. Recurring payroll happens via bank rails.
The per-transfer API patterns below are still relevant when:
* You're paying contractors ad hoc (no recurring schedule)
* You want first-party control over which wallet receives each payment
* You're running fiat payroll (USDC → bank), where Onramper Accounts don't apply — use [Offloader Wallets](/concepts/automation/offloader-wallets) instead
## Third-party transactions
Payroll disbursements are **third-party transactions** — you're moving funds on behalf of another party (your employees or contractors). Each payee must be a verified SpherePay customer with completed KYC/KYB before they can receive funds.
Every payee must be a verified customer in SpherePay with completed KYC or KYB. Sending payroll to an unverified customer will fail at the API layer.
## Setup requirements
Before processing payroll:
1. **Verify your company** — Complete [KYB](/concepts/onboarding/business-kyb).
2. **Verify each payee** — Each employee or contractor needs [KYC](/concepts/onboarding/individual-kyc).
3. **Register payment instruments per payee:**
* [Wallets](/concepts/transfers/wallets) for stablecoin payments
* [Bank accounts](/concepts/transfers/bank-accounts) for fiat payments
## Model 1 — Stablecoin payroll
USD from your company bank account → USDC → employee wallet.
```mermaid theme={"dark"}
flowchart LR
CB["Company
bank account"] -->|USD ACH / Wire| SP["SpherePay
on-ramp"]
SP -->|USDC| EW["Employee
wallet"]
```
### Per-payee transfer
```bash theme={"dark"}
curl -X POST https://api.spherepay.co/v2/transfer \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": "5000.00",
"customer": "customer_123",
"source": {
"type": "bank_account",
"id": "bankAccount_company",
"currency": "usd",
"network": "ach"
},
"destination": {
"type": "wallet",
"id": "wallet_123",
"currency": "usdc",
"network": "sol"
}
}'
```
### Batch processing
For multiple employees, fan out transfers in parallel:
```javascript theme={"dark"}
const employees = [
{ customerId: "customer_employee_1", amount: "5000.00", walletId: "wallet_1" },
{ customerId: "customer_employee_2", amount: "4500.00", walletId: "wallet_2" },
{ customerId: "customer_employee_3", amount: "6000.00", walletId: "wallet_3" },
];
const transfers = await Promise.all(
employees.map(emp =>
createTransfer({
amount: emp.amount,
customer: emp.customerId,
source: {
type: "bank_account",
id: COMPANY_BANK_ID,
currency: "usd",
network: "ach",
},
destination: {
type: "wallet",
id: emp.walletId,
currency: "usdc",
network: "sol",
},
})
)
);
```
## Model 2 — Fiat payroll (off-ramp)
USDC from your company wallet → USD → employee bank account.
```mermaid theme={"dark"}
flowchart LR
CW["Company
wallet (USDC)"] -->|USDC| SP["SpherePay
off-ramp"]
SP -->|USD ACH / Wire| EB["Employee
bank account"]
```
### Per-payee transfer
```bash theme={"dark"}
curl -X POST https://api.spherepay.co/v2/transfer \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": "5000.00",
"customer": "customer_123",
"source": {
"type": "wallet",
"id": "wallet_company",
"currency": "usdc",
"network": "ethereum"
},
"destination": {
"type": "bank_account",
"id": "bankAccount_123",
"currency": "usd",
"network": "ach"
}
}'
```
## Automated recurring payroll
For recurring payroll, use [Offloader Wallets](/concepts/automation/offloader-wallets) or [Onramper Accounts](/concepts/automation/onramper-accounts) instead of one-off transfers:
1. Create an Offloader Wallet or Onramper Account for each employee.
2. Send stablecoins to their Offloader Wallet (or fiat to their Onramper Account).
3. SpherePay auto-converts to the destination currency and deposits on the configured rail — no per-payment API call required.
## Compliance considerations
* **KYC every payee.** Every recipient must be a verified customer.
* **Collect tax information.** You're responsible for tax reporting (W-9, W-8BEN, etc.).
* **Document transfers.** Maintain records of all payroll disbursements.
## Best practices
* **Batch carefully.** Group transfers to minimize API calls but watch for rate limits.
* **Handle failures gracefully.** Build retry logic for failed transfers.
* **Use idempotency keys.** Prevent duplicate payments.
* **Communicate with payees.** Let employees know expected settlement times.
## Settlement times
| Payment type | Method | Settlement |
| - | - | - |
| USD → USDC | ACH | Same/next business day |
| USD → USDC | Wire | Within 30 minutes |
| EUR → USDC | SEPA | Same/next business day |
| USDC → USD | ACH | Same/next business day |
| USDC → USD | Wire | Within 30 minutes |
| USDC → EUR | SEPA | Same/next business day |
## Related
Create on-demand on-ramp and off-ramp transfers with full control over timing and amount.
Automated stablecoin-to-fiat transfers using dedicated wallet addresses.
Automated fiat-to-stablecoin conversions using virtual bank accounts.
Onboarding employees and contractors.
# Fiat Currency Trading with Stablecoin Settlement
Source: https://docs.spherepay.co/solutions/trading
Trade between fiat currency pairs using stablecoins as the settlement bridge — BRL ↔ USD, USD ↔ EUR, and other corridors as SpherePay's rails expand.
Use SpherePay to facilitate currency trading between fiat pairs using stablecoins as the settlement layer. The pattern is the same across corridors — a stablecoin sandwich routes the trade through USDC (or another stablecoin), with SpherePay handling both fiat legs.
This is the right pattern for FX brokerages, currency-exchange platforms, B2B cross-currency settlement, and any product that needs to convert one fiat into another fast and predictably.
## The pattern, in one shape
Every corridor below follows the same three-step model:
1. **On-ramp** the source fiat to USDC.
2. **Bridge** the USDC across networks if needed (one wallet to another).
3. **Off-ramp** USDC to the destination fiat.
The cleanest implementation uses an [Offloader Wallet](/concepts/automation/offloader-wallets) for step 3 — Sphere automatically converts USDC to the destination fiat and wires it to a bank account, eliminating the need for a second transfer API call.
Both fiat legs require separate verification profiles on the customer (one per currency). See each corridor below for the specific profiles required.
## Supported corridors
Expand a corridor below for the full step-by-step integration walkthrough.
The most common Sphere corridor today. Moves funds between Brazilian Reais (via PIX) and US Dollars (via wire or ACH) with USDC as the bridge currency.
**Common use cases:**
* Import/export payments — Brazilian importers paying USD invoices to international suppliers, or exporters receiving USD proceeds.
* Loan repayments — borrowers holding BRL servicing USD-denominated debt.
* Real estate transactions — buyers or sellers in Brazilian property deals settling in USD.
* Remittances — Brazilian senders, US recipients.
### Prerequisites
The customer must be approved for **both** verification profiles:
* `kyc_profile_a` / `kyb_profile_a` — required for USD transfers
* `kyc_profile_b` / `kyb_profile_b` — required for BRL/PIX transfers
See [Individual KYC](/concepts/onboarding/individual-kyc) and [Business KYB](/concepts/onboarding/business-kyb) for setup.
Passing `"network": "sol"` on a BRL transfer will result in a rejected request. Use `ethereum`, `polygon`, `arbitrum`, `base`, or `avalanche` for USDC. USDT supports `ethereum` or `tron`.
### Flow
```mermaid theme={"dark"}
sequenceDiagram
participant U as End user
participant I as Your integration
participant S as SpherePay
participant P as PIX network
participant B as USD bank
I->>S: POST /v2/bank-account (USD)
S-->>I: usdBankAccountId
I->>S: POST /v2/offloader-wallet (USDC → USD)
S-->>I: offloaderWallet.address
I->>S: POST /v2/wallet (register offloader address)
S-->>I: walletId
I->>S: POST /v2/bank-account (BRL + PIX key)
S-->>I: brlBankAccountId
I->>S: POST /v2/transfer (BRL → USDC on-ramp)
S-->>I: transferId, instructions.pixKey
I->>U: Present PIX payment instructions
U->>P: Send BRL via PIX
P->>S: BRL received
Note over S: Convert BRL → USDC, deliver to Offloader Wallet
Note over S: Offloader Wallet auto-converts USDC → USD
S->>B: Settle USD via wire
```
### Step 1 — Create a USD bank account
**POST** `https://api.spherepay.co/v2/bank-account`
```json theme={"dark"}
{
"customerId": "{{customer_id}}",
"bankName": "Chase",
"accountName": "My USD Account",
"accountOwner": "Alice Johnson",
"currency": "usd",
"accountDetails": {
"accountNumber": "1234567890",
"routingNumber": "021000021",
"accountType": "checking"
},
"networks": ["wire"]
}
```
### Step 2 — Create an Offloader Wallet
**POST** `https://api.spherepay.co/v2/offloader-wallet`
```json theme={"dark"}
{
"customerId": "{{customer_id}}",
"currency": "usdc",
"network": "polygon",
"destination": {
"bankAccountId": "{{usd_bank_account_id}}",
"currency": "usd",
"network": "wire"
}
}
```
Store the returned `address` as `{{offloader_address}}`.
### Step 3 — Register the Offloader address as a wallet
**POST** `https://api.spherepay.co/v2/wallet`
```json theme={"dark"}
{
"customerId": "{{customer_id}}",
"network": "polygon",
"address": "{{offloader_address}}"
}
```
Store the returned `id` as `{{wallet_id}}`. Steps 1–3 only need to be done once per customer — reuse `{{wallet_id}}` for all future BRL → USD transfers.
### Step 4 — Create a BRL bank account
**POST** `https://api.spherepay.co/v2/bank-account`
```json theme={"dark"}
{
"currency": "brl",
"bankName": "Banco do Brasil",
"accountName": "My BRL Account",
"accountDetails": {
"pixKey": "alice@example.com",
"pixKeyType": "email"
},
"accountOwner": {
"accountHolderName": "Alice Johnson",
"address": {
"line1": "Rua das Flores 123",
"city": "São Paulo",
"state": "SP",
"postalCode": "01310-100",
"country": "BRA"
}
},
"networks": ["pix"]
}
```
`pixKeyType` accepts `email`, `phone`, `cnpj`, or `random`.
### Step 5 — Create the BRL → USDC transfer
**POST** `https://api.spherepay.co/v2/transfer`
```json theme={"dark"}
{
"customer": "{{customer_id}}",
"amount": "100.00",
"source": {
"type": "bank_account",
"id": "{{brl_bank_account_id}}",
"currency": "brl",
"network": "pix"
},
"destination": {
"type": "wallet",
"id": "{{wallet_id}}",
"currency": "usdc",
"network": "polygon"
},
"integratorBpsFeeRate": "50"
}
```
**BRL constraints:** `integratorBpsFeeRate` is required (fixed fees not supported), per-transfer limits R$1.00–R$7,500.00, Solana not supported. See [Supported rails](/concepts/transfers/supported-rails).
### Step 6 — Fund the transaction
The response includes an `instructions.pixKey` — present this to your end user. Once they send the BRL via PIX:
1. Sphere receives the BRL and converts it to USDC.
2. USDC is delivered to the Offloader Wallet.
3. The Offloader Wallet auto-converts USDC to USD and wires it to the registered USD bank account.
### Reverse direction — USD → BRL
For the opposite direction, flip the pattern: register a BRL Offloader Wallet (USDC → BRL via PIX), then on-ramp USD → USDC into it. Same primitives, same number of API calls.
### Error handling
| Scenario | What happens | What to do |
| - | - | - |
| On-ramp fails | BRL returned via PIX. No USDC minted. | Retry or surface to user. |
| On-ramp succeeds, Offloader conversion fails | USDC sits in Offloader Wallet. No USD sent. | Contact SpherePay support. |
| Customer sends wrong PIX amount | Partial or excess USDC lands in Offloader Wallet. | Wallet converts whatever arrives — reconcile on your side. |
Convert between US Dollars (via ACH or wire) and Euros (via SEPA) with USDC or EURC as the bridge. SEPA settles same/next business day; SEPA Instant is near-instant where supported.
**Common use cases:**
* US importers paying European suppliers.
* European platforms accepting USD revenue and settling to EUR.
* Cross-border treasury — companies operating in both regions managing FX exposure.
* FX brokerages offering retail USD/EUR conversion.
### Prerequisites
The customer must be approved for both USD and EUR verification profiles. Both fiat rails fall under `kyc_profile_a` / `kyb_profile_a` (no separate profile is needed for EUR, unlike BRL).
All standard USDC networks are supported, including Solana. For EURC, supported networks are Base, Ethereum, and Solana.
### Flow
```mermaid theme={"dark"}
sequenceDiagram
participant U as End user
participant I as Your integration
participant S as SpherePay
participant USB as USD bank
participant EUB as EUR bank
I->>S: POST /v2/bank-account (EUR + SEPA)
S-->>I: eurBankAccountId
I->>S: POST /v2/offloader-wallet (USDC → EUR)
S-->>I: offloaderWallet.address
I->>S: POST /v2/wallet (register offloader)
S-->>I: walletId
I->>S: POST /v2/bank-account (USD)
S-->>I: usdBankAccountId
I->>S: POST /v2/transfer (USD → USDC on-ramp)
USB-->>S: USD received via ACH/wire
Note over S: Convert USD → USDC, deliver to Offloader Wallet
Note over S: Offloader auto-converts USDC → EUR
S->>EUB: Settle EUR via SEPA
```
### Step 1 — Create an EUR bank account
**POST** `https://api.spherepay.co/v2/bank-account`
```json theme={"dark"}
{
"customerId": "{{customer_id}}",
"bankName": "Deutsche Bank",
"accountName": "EUR settlement",
"accountOwner": "Acme GmbH",
"currency": "eur",
"accountDetails": {
"iban": "DE89370400440532013000",
"bic": "DEUTDEFFXXX"
},
"networks": ["sepa"]
}
```
Use `sepaInstant` in `networks` if you need near-instant settlement and the recipient bank supports it.
### Step 2 — Create an Offloader Wallet for USDC → EUR
**POST** `https://api.spherepay.co/v2/offloader-wallet`
```json theme={"dark"}
{
"customerId": "{{customer_id}}",
"currency": "usdc",
"network": "ethereum",
"destination": {
"bankAccountId": "{{eur_bank_account_id}}",
"currency": "eur",
"network": "sepa"
}
}
```
Pick the source network based on where you'll be receiving USDC. Solana is the cheapest and fastest; Ethereum and Base are common for institutional flows.
Store the returned `address` as `{{offloader_address}}`.
### Step 3 — Register the Offloader address as a wallet
**POST** `https://api.spherepay.co/v2/wallet`
```json theme={"dark"}
{
"customerId": "{{customer_id}}",
"network": "ethereum",
"address": "{{offloader_address}}"
}
```
Store the returned `id` as `{{wallet_id}}`.
### Step 4 — Create a USD bank account
**POST** `https://api.spherepay.co/v2/bank-account`
```json theme={"dark"}
{
"customerId": "{{customer_id}}",
"bankName": "Chase",
"accountName": "USD source",
"accountOwner": "Acme Inc",
"currency": "usd",
"accountDetails": {
"accountNumber": "1234567890",
"routingNumber": "021000021",
"accountType": "checking"
},
"networks": ["ach", "wire"]
}
```
### Step 5 — Create the USD → USDC transfer
**POST** `https://api.spherepay.co/v2/transfer`
```json theme={"dark"}
{
"customer": "{{customer_id}}",
"amount": "10000.00",
"source": {
"type": "bank_account",
"id": "{{usd_bank_account_id}}",
"currency": "usd",
"network": "wire"
},
"destination": {
"type": "wallet",
"id": "{{wallet_id}}",
"currency": "usdc",
"network": "ethereum"
}
}
```
Pick `wire` for large or international transfers; `ach` is the cheaper option for domestic US transfers and is sent same-day by default.
### Step 6 — Settlement
Once USD arrives at SpherePay:
1. Sphere converts USD → USDC.
2. USDC is delivered to the Offloader Wallet.
3. Offloader Wallet auto-converts USDC → EUR and sends via SEPA.
Total time: minutes (wire) to \~1 business day (ACH + SEPA), depending on rail choices.
### Reverse direction — EUR → USD
Same primitives, flipped. Register a USD Offloader Wallet (USDC → USD), on-ramp EUR → USDC into it.
### Using EURC instead of USDC
For EUR-heavy flows, EURC can reduce slippage. Set `currency: "eurc"` and `network` to one of `base`, `ethereum`, or `solana` on the Offloader Wallet. The bridge currency becomes EURC instead of USDC; the rest of the integration is identical.
## What you handle vs. what SpherePay handles
| Responsibility | Owner |
| - | - |
| Creating the customer, bank accounts, and wallet objects | You (integrator) |
| Creating and managing the Offloader Wallet | You (integrator) |
| Initiating the on-ramp transfer | You (integrator) |
| Fiat collection and source-fiat → USDC conversion | SpherePay |
| USDC → destination-fiat conversion and settlement | SpherePay (via Offloader Wallet) |
| Handling failures on either leg | You (integrator) |
## Constraints common to all corridors
* Each leg is billed as a separate transfer.
* Offloader Wallets don't support webhooks today — poll `GET /v2/transfer/{id}` to track on-ramp status.
* Both source-fiat and destination-fiat verification profiles must be approved before either transfer can be initiated.
## Looking ahead — single-call cross-currency transfers
Today every corridor requires setting up an Offloader Wallet plus a separate on-ramp transfer call. SpherePay is working toward a **single-call cross-currency transfer** API that abstracts the stablecoin hop entirely — you'd send one request with the source and destination currencies, and SpherePay would handle the routing internally. When that ships, the patterns in this guide will still work, but you'll have the option to consolidate to a single call.
## Related
How automated stablecoin-to-fiat conversion works.
Full transfer creation and tracking reference.
Per-currency network and rail compatibility.
Multi-party trade payment patterns.
# Treasury Management with Fiat-to-Stablecoin Conversion
Source: https://docs.spherepay.co/solutions/treasury-management
Convert company funds between fiat and stablecoins for treasury operations — yield, faster settlement, or multi-currency strategy.
Use SpherePay to manage your company's treasury by converting between USD and stablecoins. This enables you to hold funds in stablecoins for yield, faster settlement, or as part of a multi-currency treasury strategy.
## Overview
Treasury management with SpherePay involves three motions:
1. **On-ramping** — Converting USD from your bank account to USDC or USDT.
2. **Off-ramping** — Converting stablecoins back to USD when fiat is needed.
3. **Monitoring** — Tracking balances and transfer status across both rails.
```mermaid theme={"dark"}
flowchart LR
BA["Company
bank account"] -->|On-ramp (USD → USDC)| SP["SpherePay"]
SP -->|USDC| W["Company
wallet"]
W -->|Off-ramp (USDC → USD)| SP
SP -->|USD| BA
```
## First-party transactions
Treasury operations are typically **first-party transactions** — you're moving funds on behalf of your own legal entity, not a third party.
First-party transactions still require KYB verification. Complete [business verification](/concepts/onboarding/business-kyb) for your company before executing transfers.
## Implementation options
### Option 1 — Manual transfers via API
Best for ad-hoc conversions based on market conditions or cash needs.
```bash theme={"dark"}
# Convert $10,000 USD to USDC on Solana
curl -X POST https://api.spherepay.co/v2/transfer \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": "10000.00",
"customer": "customer_your_company",
"source": {
"type": "bank_account",
"id": "bankAccount_xxx",
"currency": "usd",
"network": "ach"
},
"destination": {
"type": "wallet",
"id": "wallet_xxx",
"currency": "usdc",
"network": "sol"
}
}'
```
### Option 2 — Virtual accounts for automated on-ramping
Best for regular deposits that should automatically convert to stablecoins.
1. [Create an Onramper Account](/concepts/automation/onramper-accounts) linked to your customer.
2. Wire funds to the virtual account's bank details.
3. SpherePay automatically converts deposits to stablecoins.
### Option 3 — Offloader Wallet for automated off-ramping
Best for stablecoin payouts that should automatically convert to fiat.
1. [Create an Offloader Wallet](/concepts/automation/offloader-wallets) linked to your customer.
2. Send stablecoins to the Offloader Wallet address.
3. SpherePay automatically converts to USD and deposits to your bank.
## Example — round-trip treasury flow
### Step 1 — On-ramp (USD → USDC)
```bash theme={"dark"}
curl -X POST https://api.spherepay.co/v2/transfer \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": "50000.00",
"customer": "customer_your_company",
"source": {
"type": "bank_account",
"id": "bankAccount_xxx",
"currency": "usd",
"network": "wire"
},
"destination": {
"type": "wallet",
"id": "wallet_xxx",
"currency": "usdc",
"network": "ethereum"
}
}'
```
### Step 2 — Hold and manage USDC
Store USDC in your registered wallet. You can:
* Transfer between wallets on different networks.
* Send to external addresses.
* Monitor balances via the dashboard or API.
### Step 3 — Off-ramp (USDC → USD)
When you need fiat:
```bash theme={"dark"}
curl -X POST https://api.spherepay.co/v2/transfer \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": "25000.00",
"customer": "customer_your_company",
"source": {
"type": "wallet",
"id": "wallet_xxx",
"currency": "usdc",
"network": "ethereum"
},
"destination": {
"type": "bank_account",
"id": "bankAccount_xxx",
"currency": "usd",
"network": "wire"
}
}'
```
## Monitoring and reporting
### Via dashboard
1. Navigate to **Transfers** to view all treasury movements.
2. Filter to show only your company's transactions.
3. Export reports for accounting.
### Via API
```bash theme={"dark"}
# Get all transfers for your company
curl "https://api.spherepay.co/v2/transfer?customer=customer_your_company" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Best practices
* **Use dedicated customer records.** Create a separate customer for treasury vs. customer-facing operations to keep reporting clean.
* **Poll for status.** Use `GET /v2/transfer/{id}` to check transfer and deposit status.
* **Plan for settlement times.** ACH takes 1–2 business days; wires are same/next day.
* **Pre-fund for liquidity needs.** If you'll need fiat on a specific day, off-ramp ahead of the deadline rather than at the moment of need.
## Related
Full transfer creation and tracking reference.
Automated on-ramping via virtual bank accounts.
Automated off-ramping from a dedicated wallet address.
KYB requirements for treasury operations.