Developers
Accept payments on your own site with REST v1 (tokenize cards in the browser, charge on your server), or send buyers to Fraction hosted checkout for invoices, split payments, and retainers (recurring billing with billing cycle day and saved card). Examples match the live production API.
Last updated 2026-09-30
Overview
Fraction exposes a versioned JSON API for programmatic one-time card payments, plus hosted pages for richer invoice flows. All v1 resources use predictable id prefixes: fr_pay_, fr_cus_, fr_evt_.
Production API. https://api.yourfraction.co/v1 (rewrites to /api/v1 on the app). Checkout. Customer pay links live at https://www.yourfraction.co/pay/{invoiceId}.
Two integration modes.
# Production API base
curl -sS 'https://api.yourfraction.co/v1/payments/fr_pay_…' \
-H 'Authorization: Bearer fr_live_99823a7f4c2e91d0b6e4c8f1a2b3d5e7'{
"object": "payment",
"status": "completed"
}Recipes
One-time sale on your site.
POST /v1/payments with source.type = card_token.payment.updated webhooks or poll GET until status is completed.Storefront with catalog. Upsert products from your shop via PUT /v1/products/by-sku/…, display prices from GET /v1/products, checkout with payments or payment links.
Recurring billing (SaaS, memberships). From your backend, call POST /v1/invoices with billing_type: retainer, billing_interval, and optional billing_cycle_day. Send the customer pay_url or collect a card on your site and post to the invoice pay endpoint. Fraction saves the card and runs renewals; your app listens for retainer webhooks to grant or revoke access.
curl -sS -X POST 'https://api.yourfraction.co/v1/payments' \
-H 'Authorization: Bearer fr_live_99823a7f4c2e91d0b6e4c8f1a2b3d5e7' \
-H 'Content-Type: application/json' \
-d '{
"amount": 20000,
"currency": "usd",
"customer": {
"email": "jordan@example.com",
"name": "Jordan Lee"
},
"source": {
"type": "card_token",
"token": "TKxxxxxxxxxxxxxxxx"
},
"fraud_session_id": "optional_fraud_session_id"
}'{
"id": "fr_pay_892347c1e4a0b2f901234567890123456",
"object": "payment",
"amount": 20000,
"currency": "usd",
"status": "processing",
"created": "2025-09-24T18:04:00.000Z",
"card_brand": null,
"last4": null
}Like Stripe test/live keys
Fraction splits test and live the same way Stripe does: the API key prefix and host you call decide whether money is real. Your app code stays the same; env vars swap base URL and secret key.
fr_test_… from Dashboard → Settings → API keys → Generate test key on staging. Call https://staging.yourfraction.co/api/v1. Pay links use https://staging.yourfraction.co/pay/{invoiceId}. Card fields use sandbox and test card 4242 4242 4242 4242.fr_live_… on production. Call https://api.yourfraction.co/v1. Checkout at https://www.yourfraction.co/pay/…. Card fields use prod.Enforced: a test key returns 403 on production API, and a live key returns 403 on staging. Do not put a live club key on your staging site unless you intend to take real payments.
Webhooks: register separate endpoints on staging vs production (or filter by event in your handler). Signing secrets are per endpoint in each environment.
Your own app sandbox: if you fake checkout locally (4242…) without calling Fraction, that is separate from Fraction test mode. To exercise real Fraction APIs without money, use fr_test_… against staging, not a live key against production.
# Staging / sandbox (no real money)
curl -sS 'https://staging.yourfraction.co/api/v1/payments' \
-H 'Authorization: Bearer fr_test_99823a7f4c2e91d0b6e4c8f1a2b3d5e7' \
-H 'Content-Type: application/json' \
-d '{ ... }'
# Production (real cards, real money)
curl -sS 'https://api.yourfraction.co/v1/payments' \
-H 'Authorization: Bearer fr_live_99823a7f4c2e91d0b6e4c8f1a2b3d5e7' \
-H 'Content-Type: application/json' \
-d '{ ... }'API v1
Issue keys from Dashboard → Settings → API keys. Use Authorization: Bearer fr_live_… on production and fr_test_… on staging (see Test vs live). Invoice pay endpoints are public and keyed by invoice UUID instead. Never expose secret keys in the browser.
api_error means the workspace is not cleared to process payments yet.curl -sS 'https://api.yourfraction.co/v1/payments/fr_pay_…' \
-H 'Authorization: Bearer fr_live_99823a7f4c2e91d0b6e4c8f1a2b3d5e7'{
"error": {
"type": "authentication_error",
"message": "Invalid or revoked API key."
}
}Browser · Fraction payment script
The API never accepts PAN or CVV. Mount FractionPayments.PaymentForm with your Fraction payment application ID and environment (sandbox or prod). On submit, send response.data.id (token) to your server.
Initialize FractionPayments.Auth with your Fraction merchant ID and pass fraud_session_id on charges. Dashboard meta exposes paymentAppId and your workspace merchant ID for logged-in merchants building custom UI.
Sandbox. Test card 4242 4242 4242 4242, any future expiry, any CVC, with test keys and sandbox tokenization only.
<!-- Browser only: never call POST /v1/payments from client-side code -->
<script src="https://www.yourfraction.co/payment/v2.js"></script>
<script src="https://www.yourfraction.co/payment/fraction-sdk.js"></script>
<div id="fraction-card-form"></div>
<script>
const paymentEnv = "sandbox"; // or "prod" in production
const paymentAppId = "YOUR_FRACTION_PAYMENT_APPLICATION_ID";
const paymentMerchantId = "YOUR_FRACTION_MERCHANT_ID"; // for fraud session
const form = FractionPayments.PaymentForm("fraction-card-form", paymentEnv, paymentAppId, {
showAddress: true,
showLabels: true,
onLoad: () => console.log("form ready"),
});
FractionPayments.Auth(paymentEnv, paymentMerchantId, (sessionKey) => {
window.__fractionFraudSessionId = sessionKey;
});
function pay() {
form.submit(async (err, response) => {
if (err) return;
const token = response?.data?.id;
const fraudSessionId = window.__fractionFraudSessionId;
// POST token to YOUR backend; your server calls POST /v1/payments with Bearer key.
await fetch("/api/your-server/create-fraction-payment", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
amount: 20000,
customer: { email: "buyer@example.com", name: "Buyer Name" },
token,
fraud_session_id: fraudSessionId,
}),
});
});
}
</script>POST /v1/customers
Optional but recommended for repeat buyers. Returns fr_cus_<uuid> for use in later payments via customer: { "id": "fr_cus_…" }.
curl -sS -X POST 'https://api.yourfraction.co/v1/customers' \
-H 'Authorization: Bearer fr_live_99823a7f4c2e91d0b6e4c8f1a2b3d5e7' \
-H 'Content-Type: application/json' \
-d '{
"email": "sam@example.com",
"name": "Sam Rivera",
"fraud_session_id": "optional_fraud_session_id"
}'{
"id": "fr_cus_88219d4c3a7e01234567890123456789",
"object": "customer",
"email": "sam@example.com",
"name": "Sam Rivera"
}POST /v1/payments
Captures a one-time card payment. Amounts are integer cents. Currency defaults to USD when omitted.
Required. amount, customer, source with type: "card_token" and token from the payment script.
Status values. pending, processing, completed, failed, canceled, refunded. Failed payments may include failure_reason.
Deposit/balance split checkout is an invoice feature (dashboard), not a field on this endpoint.
curl -sS -X POST 'https://api.yourfraction.co/v1/payments' \
-H 'Authorization: Bearer fr_live_99823a7f4c2e91d0b6e4c8f1a2b3d5e7' \
-H 'Content-Type: application/json' \
-d '{
"amount": 20000,
"currency": "usd",
"customer": {
"email": "jordan@example.com",
"name": "Jordan Lee"
},
"source": {
"type": "card_token",
"token": "TKxxxxxxxxxxxxxxxx"
},
"fraud_session_id": "optional_fraud_session_id"
}'{
"id": "fr_pay_892347c1e4a0b2f901234567890123456",
"object": "payment",
"amount": 20000,
"currency": "usd",
"status": "processing",
"created": "2025-09-24T18:04:00.000Z",
"card_brand": null,
"last4": null
}curl -sS -X POST 'https://api.yourfraction.co/v1/payments' \
-H 'Authorization: Bearer fr_live_99823a7f4c2e91d0b6e4c8f1a2b3d5e7' \
-H 'Content-Type: application/json' \
-d '{
"amount": 15000,
"currency": "usd",
"customer": { "id": "fr_cus_88219d4c3a7e01234567890123456789" },
"source": { "type": "card_token", "token": "TKxxxxxxxxxxxxxxxx" }
}'GET /v1/payments/[id]
Returns the payment object for fr_pay_<uuid> in your workspace, refreshed from the processor. Use after webhooks or when polling before fulfillment.
curl -sS 'https://api.yourfraction.co/v1/payments/fr_pay_892347c1e4a0b2f901234567890123456' \
-H 'Authorization: Bearer fr_live_99823a7f4c2e91d0b6e4c8f1a2b3d5e7'{
"id": "fr_pay_892347c1e4a0b2f901234567890123456",
"object": "payment",
"amount": 20000,
"currency": "usd",
"status": "completed",
"created": "2025-09-24T18:04:00.000Z",
"card_brand": "Visa",
"last4": "4242"
}POST /v1/invoices
Create SaaS subscriptions, memberships, and one-time checkout invoices from your server (no dashboard required). Retainers use billing_type: retainer, billing_interval: MONTHLY, and optional billing_cycle_day. Redirect members or customers to pay_url.
See Webhooks for invoice and retainer events, and Product catalog if you sync SKUs from your storefront.
curl -sS -X POST 'https://api.yourfraction.co/v1/invoices' \
-H 'Authorization: Bearer fr_live_99823a7f4c2e91d0b6e4c8f1a2b3d5e7' \
-H 'Idempotency-Key: member-enroll-USER_123' \
-H 'Content-Type: application/json' \
-d '{
"customer_name": "Alex Member",
"customer_email": "alex@example.com",
"due_date": "2025-10-01",
"billing_type": "retainer",
"billing_interval": "MONTHLY",
"billing_cycle_day": 1,
"send_email": true,
"line_items": [
{ "description": "Monthly membership", "quantity": 1, "unit_price_cents": 2900 }
]
}'{
"id": "660e8400-e29b-41d4-a716-446655440000",
"object": "invoice",
"billing_type": "retainer",
"billing_interval": "MONTHLY",
"billing_cycle_day": 1,
"pay_url": "https://www.yourfraction.co/pay/660e8400-e29b-41d4-a716-446655440000",
"status": "sent",
"amount": 2900
}Shop sync · Dashboard Products
Merchants manage products in Dashboard → Products (/dashboard/product-catalog). The same catalog is available over API v1 so your storefront, Shopify app, or agency site stays in sync with Fraction.
SKU is the sync key. Each workspace enforces unique SKUs. When your shop creates or updates a variant, call PUT /v1/products/by-sku/{SKU} (upsert). Changes appear in the Fraction dashboard immediately. To pull dashboard edits into your site, poll GET /v1/products or refetch after staff update prices in Fraction.
Fields. name, sku, price_cents, optional description, image_url, and metadata (productType, category, inventory flags). Product ids use fr_prod_<uuid>.
Charge from a catalog item. POST /v1/products/{id}/payment-link builds an invoice at the current catalog price (× quantity) and returns pay_url. For instant cart checkout on your domain, read price_cents then charge with POST /v1/payments. Set send_email: false to only receive the link without emailing the buyer.
curl -sS -X POST 'https://api.yourfraction.co/v1/products/fr_prod_550e8400-e29b-41d4-a716-446655440000/payment-link' \
-H 'Authorization: Bearer fr_live_99823a7f4c2e91d0b6e4c8f1a2b3d5e7' \
-H 'Content-Type: application/json' \
-d '{
"customer_name": "Jordan Lee",
"customer_email": "jordan@example.com",
"due_date": "2025-10-01",
"quantity": 2,
"send_email": true
}'{
"object": "payment_link",
"invoice_id": "660e8400-e29b-41d4-a716-446655440001",
"pay_url": "https://www.yourfraction.co/pay/660e8400-e29b-41d4-a716-446655440001",
"product_id": "fr_prod_550e8400-e29b-41d4-a716-446655440000",
"amount": 30000,
"currency": "usd",
"quantity": 2
}curl -sS 'https://api.yourfraction.co/v1/products' \
-H 'Authorization: Bearer fr_live_99823a7f4c2e91d0b6e4c8f1a2b3d5e7'{
"object": "list",
"data": [
{
"id": "fr_prod_550e8400-e29b-41d4-a716-446655440000",
"object": "catalog_product",
"name": "Consultation (60 min)",
"sku": "SVC-60MIN",
"price_cents": 15000,
"currency": "usd",
"image_url": "https://cdn.example.com/consult.jpg",
"metadata": {
"productType": "SERVICE",
"category": "Services",
"inventoryInfinite": true,
"inventoryQuantity": null
}
}
]
}curl -sS -X PUT 'https://api.yourfraction.co/v1/products/by-sku/SVC-60MIN' \
-H 'Authorization: Bearer fr_live_99823a7f4c2e91d0b6e4c8f1a2b3d5e7' \
-H 'Content-Type: application/json' \
-d '{
"name": "Consultation (60 min)",
"sku": "SVC-60MIN",
"price_cents": 15000,
"description": "Video call with our team",
"image_url": "https://cdn.example.com/consult.jpg",
"metadata": {
"productType": "SERVICE",
"category": "Services",
"inventoryInfinite": true,
"inventoryQuantity": null
}
}'Pay links
Create invoices in the Fraction dashboard (one-time or retainer). Customers pay at /pay/{invoiceId} with card, bank (where enabled), or wallets on the hosted page.
Custom checkout UI. You may tokenize with the Fraction payment script on your domain and POST to POST /api/invoices/{invoiceId}/pay with token and fraudSessionId. Protect invoice UUIDs as capability secrets.
# Pay URL (after creating an invoice in the dashboard)
https://www.yourfraction.co/pay/550e8400-e29b-41d4-a716-446655440000Subscriptions via invoices
Monthly membership flow. (1) Your server POST /v1/invoices with billing_type: retainer and billing_interval: MONTHLY → save the invoice id (UUID). (2) Browser: Fraction payment fields tokenize the card and FractionPayments.Auth returns fraudSessionId. (3) Your server posts to POST /api/invoices/{invoiceId}/pay with real token and fraudSessionId (no Bearer key). The doc curl is not runnable with placeholder values; fake tokens fail validation and you may see 404 if the UUID is not a real invoice.
Retainers are recurring subscriptions modeled as invoices. In API v1 use billing_type: retainer and billing_interval of WEEKLY, BIWEEKLY, MONTHLY, or YEARLY. This fits SaaS plans, memberships, and other software billed on a schedule. Split deposits are not allowed on retainers.
Card only at checkout. Bank/ACH instruments are rejected for retainer authorization. Wallets on the hosted page still resolve to a card-backed instrument where supported.
Monthly billing cycle day. When creating a monthly retainer, set billing_cycle_day (1 to 31). Checkout verifies the card; the first full recurring charge aligns to the next occurrence of that calendar day (avoiding mid-month proration). Customers see the next charge date on the pay page.
After success, invoice status becomes subscription_active. The dashboard shows next billing date, saved card brand/last4, Fraction retainer ID (invoice UUID), a card update link that collects a new card without charging, and Charge today for a one-time off-cycle charge using the saved card (merchant session auth). Invoice lookup and retainer webhooks include card_brand, last4, and card_update_url. Partners should use those — not a new invoice — when a member updates their card.
Webhooks. Retainer lifecycle events (retainer.activated, retainer.charge_succeeded, etc.) and catalog catalog_product.* events are documented under Webhooks.
# After POST /v1/invoices, use the invoice "id" (UUID) below — not fr_inv_ prefix.
curl -sS -X POST 'https://www.yourfraction.co/api/invoices/660e8400-e29b-41d4-a716-446655440000/pay' \
-H 'Content-Type: application/json' \
-d '{
"token": "REAL_CARD_TOKEN_FROM_BROWSER",
"fraudSessionId": "REAL_FRAUD_SESSION_ID"
}'{
"ok": true,
"message": "Retainer authorized! Your payment method will be charged automatically on the billing schedule. Confirmation sent to your email.",
"paymentPhase": "subscription_active"
}// 404 — wrong or unknown invoice UUID
{ "ok": false, "error": "This invoice could not be found." }
// 400 — missing/invalid body (placeholder tokens / fraud session fail validation)
{ "ok": false, "error": "Valid payment details are required." }
// 402/422 — card declined or retainer rejected (ACH, etc.)
{ "ok": false, "error": "…" }Events · Dashboard or API
Add endpoints under Dashboard → Settings → Webhooks or POST /v1/webhook_endpoints. Signing secret fr_whsec_… is shown once. Header: Fraction-Signature: t=<unix>,v1=<hmac> over t.rawBody.
Payments: payment.updated, payment.completed, payment.failed. Catalog: catalog_product.*. Invoices & retainers: invoice.paid, retainer.activated, retainer.charge_succeeded, retainer.payment_method_updated, and related types. Configure filters in Dashboard → Settings → Webhooks.
Optional enabled_events per endpoint. Use Idempotency-Key on v1 POST/PUT for safe retries.
# Inbound POST to your server
POST /hooks/fraction HTTP/1.1
Host: hooks.example.com
Fraction-Signature: t=1727204400,v1=a1f2c3d4e5f6...
Content-Type: application/json
{
"id": "fr_evt_4410c2b7a9e1",
"type": "retainer.activated",
"created_at": 1727204400123,
"data": {
"object": {
"id": "660e8400-e29b-41d4-a716-446655440000",
"object": "invoice",
"status": "subscription_active",
"billing_type": "retainer",
"amount": 20000,
"currency": "usd",
"card_brand": "Visa",
"last4": "4242",
"card_update_url": "https://www.yourfraction.co/pay/660e8400-e29b-41d4-a716-446655440000?update_card=1"
}
}
}Checklist
Common mistakes to avoid:
succeeded instead of completed for payment status.split_payment on POST /v1/payments (not supported).source: { type, token }.fraction.payment.succeeded webhook names (use payment.updated).sku (causes 409 conflicts).curl -sS 'https://api.yourfraction.co/v1/payments/fr_pay_…' \
-H 'Authorization: Bearer fr_live_99823a7f4c2e91d0b6e4c8f1a2b3d5e7'{
"id": "fr_pay_892347c1e4a0b2f901234567890123456",
"object": "payment",
"amount": 20000,
"currency": "usd",
"status": "completed",
"created": "2025-09-24T18:04:00.000Z",
"card_brand": "Visa",
"last4": "4242"
}