Payments
All endpoints are prefixed with /v1. Unless otherwise noted, all endpoints require authentication via Authorization: Bearer <access_token> or X-API-Key.
Wallet
Section titled “Wallet”Get Balance
Section titled “Get Balance”GET /v1/walletReturns the authenticated user’s wallet. If the user has no wallet record, a default object with balance: 0 is returned.
Headers:
| Header | Value | Required |
|---|---|---|
Authorization | Bearer <access_token> | Yes |
Request Body: None
Response 200 OK:
{ "wallet": { "user_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "balance": 150.00, "created_at": "2025-06-15T10:30:00.000Z", "updated_at": "2025-09-22T14:05:00.000Z" }}{ "wallet": { "balance": 0 }}Error Responses:
| Status | Body | Condition |
|---|---|---|
401 | { "error": "Not authenticated" } | Missing or invalid token |
List Transactions
Section titled “List Transactions”GET /v1/wallet/transactionsReturns a paginated list of wallet transactions in descending order (newest first). Uses cursor-based pagination.
Headers:
| Header | Value | Required |
|---|---|---|
Authorization | Bearer <access_token> | Yes |
Query Parameters:
| Parameter | Type | Required | Default | Constraints | Description |
|---|---|---|---|---|---|
cursor | string | No | — | — | Opaque cursor from a previous response for fetching the next page |
limit | integer | No | 20 | Max 50 | Number of transactions to return |
Request Body: None
Response 200 OK:
{ "transactions": [ { "id": "txn_a1b2c3d4-e5f6-7890-abcd-ef1234567890", "user_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "type": "deposit", "amount": 50.00, "status": "completed", "created_at": "2025-09-22T14:05:00.000Z" }, { "id": "txn_f6e5d4c3-b2a1-0987-dcba-0987654321fe", "user_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "type": "withdrawal", "amount": -25.00, "status": "pending", "created_at": "2025-09-20T09:12:00.000Z" } ], "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNS0wOS0yMFQwOToxMjowMC4wMDBaIn0="}| Response Field | Type | Description |
|---|---|---|
transactions | array | Array of transaction objects |
transactions[].id | string | Transaction UUID |
transactions[].user_id | string | Owner’s user UUID |
transactions[].type | string | Transaction type (e.g. "deposit", "withdrawal") |
transactions[].amount | number | Signed amount (negative for withdrawals) |
transactions[].status | string | Status: "pending", "completed", "failed" |
transactions[].created_at | string | ISO 8601 timestamp |
next_cursor | string | null | Cursor for the next page, or null if no more results |
Error Responses:
| Status | Body | Condition |
|---|---|---|
401 | { "error": "Not authenticated" } | Missing or invalid token |
Withdraw
Section titled “Withdraw”POST /v1/wallet/withdrawInitiates a withdrawal from the user’s wallet. Creates a transaction record with type="withdrawal", a negative amount, and status="pending". The balance is not immediately deducted — the actual deduction occurs during fulfillment processing.
Headers:
| Header | Value | Required |
|---|---|---|
Authorization | Bearer <access_token> | Yes |
Request Body:
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
amount | number | Yes | Must be > 0 | Amount to withdraw |
{ "amount": 25.00}Response 201 Created:
{ "transaction": { "id": "txn_f6e5d4c3-b2a1-0987-dcba-0987654321fe", "user_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "type": "withdrawal", "amount": -25.00, "status": "pending", "created_at": "2025-09-22T15:30:00.000Z" }}Error Responses:
| Status | Body | Condition |
|---|---|---|
400 | { "error": "Insufficient balance" } | Wallet balance is less than the requested amount |
400 | { "error": "Amount must be greater than 0" } | Invalid or missing amount |
401 | { "error": "Not authenticated" } | Missing or invalid token |
Deposit
Section titled “Deposit”POST /v1/wallet/depositCreates a Stripe Checkout session for depositing funds into the user’s wallet. The wallet balance is not updated immediately — it is credited when the checkout.session.completed webhook fires (see Stripe Webhooks).
Headers:
| Header | Value | Required |
|---|---|---|
Authorization | Bearer <access_token> | Yes |
Request Body:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
amount | number | Yes | — | Amount to deposit (must be > 0) |
success_url | string | No | http://localhost:3000 | URL to redirect to after successful payment |
cancel_url | string | No | — | URL to redirect to if the user cancels checkout |
{ "amount": 50.00, "success_url": "https://app.example.com/wallet?status=success", "cancel_url": "https://app.example.com/wallet?status=canceled"}Response 200 OK:
{ "checkout_url": "https://checkout.stripe.com/c/pay/cs_live_a1b2c3..."}| Response Field | Type | Description |
|---|---|---|
checkout_url | string | Stripe-hosted checkout URL. Redirect the user here. |
Error Responses:
| Status | Body | Condition |
|---|---|---|
400 | { "error": "Amount must be greater than 0" } | Invalid or missing amount |
401 | { "error": "Not authenticated" } | Missing or invalid token |
Stripe
Section titled “Stripe”Create Checkout
Section titled “Create Checkout”POST /v1/stripe/checkoutCreates a Stripe Checkout session for purchasing a seller’s product or service. The seller must have a connected Stripe account. A platform fee is calculated from the seller’s platform_fee_percent (or the system-wide default if not set). A pending payment record is created in the database.
Headers:
| Header | Value | Required |
|---|---|---|
Authorization | Bearer <access_token> | Yes |
Request Body:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
seller_id | string | Yes | — | UUID of the seller |
amount | number | Yes | — | Payment amount in the specified currency |
currency | string | No | "usd" | Three-letter ISO currency code |
description | string | Yes | — | Human-readable description of the purchase |
app_slug | string | Yes | — | Identifier of the app/extension initiating the checkout |
reference_id | string | Yes | — | External reference ID (e.g. order ID, contract ID) |
reference_type | string | Yes | — | Type of the reference (e.g. "order", "contract") |
success_url | string | Yes | — | URL to redirect to after successful payment |
cancel_url | string | Yes | — | URL to redirect to if the user cancels checkout |
{ "seller_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "amount": 99.99, "currency": "usd", "description": "Premium design template", "app_slug": "marketplace", "reference_id": "order_abc123", "reference_type": "order", "success_url": "https://app.example.com/orders/abc123?status=success", "cancel_url": "https://app.example.com/orders/abc123?status=canceled"}Response 200 OK:
{ "checkout_url": "https://checkout.stripe.com/c/pay/cs_live_x9y8z7..."}| Response Field | Type | Description |
|---|---|---|
checkout_url | string | Stripe-hosted checkout URL. Redirect the user here. |
Error Responses:
| Status | Body | Condition |
|---|---|---|
400 | { "error": "Seller does not have a connected Stripe account" } | Seller has not completed Stripe Connect onboarding |
400 | { "error": "Missing required fields" } | One or more required fields are absent |
401 | { "error": "Not authenticated" } | Missing or invalid token |
Stripe Connect Onboarding
Section titled “Stripe Connect Onboarding”POST /v1/stripe/connect/onboardInitiates or resumes Stripe Connect Express onboarding for the authenticated user. On the first call, a new Stripe Express account is created and a business_accounts record is inserted in the database. On subsequent calls, the existing Stripe account is retrieved and a fresh onboarding link is generated.
Headers:
| Header | Value | Required |
|---|---|---|
Authorization | Bearer <access_token> | Yes |
Request Body:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
return_url | string | No | — | URL to redirect to after the user completes onboarding |
refresh_url | string | No | — | URL to redirect to if the onboarding link expires |
{ "return_url": "https://app.example.com/settings/payments", "refresh_url": "https://app.example.com/settings/payments?refresh=true"}Response 200 OK:
{ "onboarding_url": "https://connect.stripe.com/setup/e/acct_1A2B3C4D5E/onboarding..."}| Response Field | Type | Description |
|---|---|---|
onboarding_url | string | Stripe-hosted onboarding URL. Redirect the user here. |
Error Responses:
| Status | Body | Condition |
|---|---|---|
401 | { "error": "Not authenticated" } | Missing or invalid token |
Connect Status
Section titled “Connect Status”GET /v1/stripe/connect/statusReturns the Stripe Connect status for the authenticated user. If the user has not started onboarding, a default object with connected: false is returned.
Headers:
| Header | Value | Required |
|---|---|---|
Authorization | Bearer <access_token> | Yes |
Request Body: None
Response 200 OK:
{ "connect": { "user_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "stripe_account_id": "acct_1A2B3C4D5E", "stripe_charges_enabled": true, "stripe_payouts_enabled": true, "stripe_onboarding_complete": true }}{ "connect": { "connected": false }}| Response Field | Type | Description |
|---|---|---|
connect.user_id | string | User UUID (only present when connected) |
connect.stripe_account_id | string | Stripe Express account ID |
connect.stripe_charges_enabled | boolean | Whether the account can accept charges |
connect.stripe_payouts_enabled | boolean | Whether the account can receive payouts |
connect.stripe_onboarding_complete | boolean | Whether onboarding has been fully completed |
connect.connected | boolean | false when the user has no Stripe Connect account |
Error Responses:
| Status | Body | Condition |
|---|---|---|
401 | { "error": "Not authenticated" } | Missing or invalid token |
Connect Dashboard
Section titled “Connect Dashboard”POST /v1/stripe/connect/dashboardGenerates a Stripe Express dashboard login link for the authenticated user. The user must have an existing connected Stripe account.
Headers:
| Header | Value | Required |
|---|---|---|
Authorization | Bearer <access_token> | Yes |
Request Body: None
Response 200 OK:
{ "dashboard_url": "https://connect.stripe.com/express/acct_1A2B3C4D5E/login..."}| Response Field | Type | Description |
|---|---|---|
dashboard_url | string | Stripe Express dashboard URL. Redirect the user here. |
Error Responses:
| Status | Body | Condition |
|---|---|---|
400 | { "error": "No connected Stripe account found" } | User has not completed Stripe Connect onboarding |
401 | { "error": "Not authenticated" } | Missing or invalid token |
Webhooks
Section titled “Webhooks”POST /v1/webhook/stripe
Section titled “POST /v1/webhook/stripe”POST /v1/webhook/stripeReceives and processes Stripe webhook events. This endpoint does not use standard API authentication. Instead, it verifies the request using the stripe-signature header and the configured Stripe webhook signing secret.
Headers:
| Header | Value | Required |
|---|---|---|
stripe-signature | Stripe webhook signature | Yes |
Content-Type | application/json | Yes |
Handled Event Types:
checkout.session.completed
Section titled “checkout.session.completed”Triggered when a Stripe Checkout session is successfully completed.
| Behavior | Description |
|---|---|
| Payment record | Updates the corresponding payment record status to "completed" |
| Wallet deposit | If the session metadata indicates a wallet deposit: inserts a wallet transaction with type="deposit" and status="completed", then increments the user’s wallet balance |
| Extension routing | If app_slug is present in the session metadata, routes the event to the appropriate extension handler for further processing |
customer.subscription.created
Section titled “customer.subscription.created”Triggered when a new subscription is created.
| Behavior | Description |
|---|---|
| Profile update | Updates the user’s profile with the new subscription_status and subscription_expires_at |
customer.subscription.updated
Section titled “customer.subscription.updated”Triggered when an existing subscription is modified (e.g. plan change, renewal).
| Behavior | Description |
|---|---|
| Profile update | Updates the user’s profile subscription_status and subscription_expires_at to reflect the new state |
customer.subscription.deleted
Section titled “customer.subscription.deleted”Triggered when a subscription is canceled or expires.
| Behavior | Description |
|---|---|
| Profile update | Sets subscription_status to "canceled" and subscription_plan to null |
account.updated
Section titled “account.updated”Triggered when a connected Stripe account’s details change.
| Behavior | Description |
|---|---|
| Business account update | Updates the business_accounts record: stripe_charges_enabled, stripe_payouts_enabled, and stripe_onboarding_complete flags |
Response 200 OK:
{ "received": true}Error Responses:
| Status | Body | Condition |
|---|---|---|
400 | { "error": "Webhook signature verification failed" } | Invalid or missing stripe-signature header |
Business
Section titled “Business”Get Business Info
Section titled “Get Business Info”GET /v1/businessReturns the authenticated user’s business account and subscription details.
Headers:
| Header | Value | Required |
|---|---|---|
Authorization | Bearer <access_token> | Yes |
Request Body: None
Response 200 OK:
{ "business_account": { "id": "ba_a1b2c3d4-e5f6-7890-abcd-ef1234567890", "user_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "stripe_account_id": "acct_1A2B3C4D5E", "stripe_charges_enabled": true, "stripe_payouts_enabled": true, "stripe_onboarding_complete": true, "platform_fee_percent": 10, "created_at": "2025-06-15T10:30:00.000Z" }, "subscription": { "plan": "pro", "status": "active", "expires_at": "2026-06-15T10:30:00.000Z" }}If the user has no business account or subscription, the respective field is null:
{ "business_account": null, "subscription": null}| Response Field | Type | Description |
|---|---|---|
business_account | object | null | Business account record, or null |
business_account.stripe_account_id | string | Stripe Express account ID |
business_account.stripe_charges_enabled | boolean | Whether charges are enabled |
business_account.stripe_payouts_enabled | boolean | Whether payouts are enabled |
business_account.stripe_onboarding_complete | boolean | Whether Stripe onboarding is complete |
business_account.platform_fee_percent | number | Custom platform fee percentage for this seller |
subscription | object | null | Subscription details, or null |
subscription.plan | string | Plan name: "starter" or "pro" |
subscription.status | string | Subscription status (e.g. "active", "canceled") |
subscription.expires_at | string | ISO 8601 expiration timestamp |
Error Responses:
| Status | Body | Condition |
|---|---|---|
401 | { "error": "Not authenticated" } | Missing or invalid token |
Upgrade Plan
Section titled “Upgrade Plan”POST /v1/business/upgradeCreates a Stripe Checkout session in subscription mode for upgrading the user’s business plan. Redirects the user to Stripe to complete payment.
Headers:
| Header | Value | Required |
|---|---|---|
Authorization | Bearer <access_token> | Yes |
Request Body:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
plan | string | Yes | — | Plan to subscribe to. Must be "starter" or "pro" |
success_url | string | No | — | URL to redirect to after successful subscription |
cancel_url | string | No | — | URL to redirect to if the user cancels checkout |
Available Plans:
| Plan | Monthly Price | Stripe Amount |
|---|---|---|
starter | $9/mo | 900 cents |
pro | $29/mo | 2900 cents |
{ "plan": "pro", "success_url": "https://app.example.com/business?status=subscribed", "cancel_url": "https://app.example.com/business?status=canceled"}Response 200 OK:
{ "checkout_url": "https://checkout.stripe.com/c/pay/cs_live_sub_a1b2c3..."}| Response Field | Type | Description |
|---|---|---|
checkout_url | string | Stripe-hosted subscription checkout URL |
Error Responses:
| Status | Body | Condition |
|---|---|---|
400 | { "error": "Invalid plan" } | plan is not "starter" or "pro" |
401 | { "error": "Not authenticated" } | Missing or invalid token |
Business Dashboard
Section titled “Business Dashboard”GET /v1/business/dashboardReturns aggregate business statistics for the authenticated user. Sales and revenue are calculated from completed payments where the user is the seller.
Headers:
| Header | Value | Required |
|---|---|---|
Authorization | Bearer <access_token> | Yes |
Request Body: None
Response 200 OK:
{ "stats": { "total_sales": 142, "total_revenue": 8549.75, "total_products": 12 }}| Response Field | Type | Description |
|---|---|---|
stats.total_sales | number | Total number of completed sales as seller |
stats.total_revenue | number | Total revenue from completed payments (in account currency) |
stats.total_products | number | Total number of products listed by the user |
Error Responses:
| Status | Body | Condition |
|---|---|---|
401 | { "error": "Not authenticated" } | Missing or invalid token |