On this page
API Reference & Integration Guide
Everything you need to collect payments, manage subscriptions, reward customers, and automate your business with FanBasis.
Production URL
https://www.fanbasis.com
QA URL
https://qa.dev-fan-basis.com

Introduction

The FanBasis API lets you build custom integrations with your FanBasis account. Instead of managing everything manually through the dashboard, you can write code that automatically creates payment pages, tracks who paid, manages subscriptions, and reacts to events in real time — all tailored to your specific workflow.

Whether you're building a custom membership portal, connecting FanBasis to your existing platform, or automating your back-office, this guide covers everything you need.

What can you build?

💳 Accept Payments
Generate payment links and checkout pages for any product in seconds — no coding required on the checkout side.
🔄 Manage Subscriptions
Create recurring billing plans, track renewals, extend access, and cancel subscriptions programmatically.
🔔 Get Real-Time Alerts
Receive instant notifications when payments succeed, subscriptions are created, or anything else changes.
👥 Manage Customers
View your full customer list, see their payment history, and charge them again without a new checkout.
🏷️ Create Discount Codes
Build and manage promotional codes — percentage or fixed discounts, expiration dates, usage limits, and more.
📊 Track Transactions
Pull detailed records of every payment — who paid, what for, your net payout after fees, and refund history.
🧪 API Playground
Test any endpoint right here in the docs — enter your API key, pick an endpoint, fill in the parameters, and hit Send. No terminal required.

Key Concepts

◆ Checkout Session

A checkout session is a payment page. You define what's being sold and at what price, and FanBasis generates a link you share with your customer. When they click it, they see a ready-to-use payment form. One session can produce many transactions over time.

◆ Webhook

A webhook is a message FanBasis sends to your server the moment something happens — like a payment succeeding or a subscription being canceled. Instead of polling the API constantly, your app just listens and reacts when events arrive.

◆ Transaction

A transaction is a single completed payment. Each time a customer pays — whether for a one-time purchase or a subscription renewal — a new transaction is created. Transactions include fee breakdowns and net payout amounts.

Authentication

Every API request must include your API key in the x-api-key header. There are no usernames or passwords — just your key.

⚠ Keep your key private

Never share your API key in public code, GitHub repositories, or client-side JavaScript. If your key is compromised, regenerate it immediately from the API Keys section in your FanBasis dashboard.

How to authenticate every request:
curl https://www.fanbasis.com/public-api/products \
  -H "x-api-key: YOUR_API_KEY"

How Responses Work

Every response follows the same structure: a status of "success" or "error", a human-readable message, and a data object with the actual result. When something goes wrong, the errors field tells you exactly which fields need fixing.

✓ Successful response
{ "status": "success",
  "message": "...",
  "data": { ... }
}
✕ Error response
{ "status": "error",
  "message": "...",
  "data": [],
  "errors": { "field": ["msg"] }
}

Quick Start

Here's the fastest path to accepting your first payment through the API. This whole flow takes about 10 minutes.

1
Get your API key
Log into your FanBasis dashboard and go to the API Keys section. Copy your API key. You'll use it in every request you make.
2
Create a checkout session
Make a POST request to /checkout-sessions with your product name, price, and type. FanBasis returns a payment_link — that's your customer's payment page.
3
Share the link with your customer
Send the payment_link URL to your customer via email, SMS, or embed it as a button. When they pay, FanBasis handles the entire checkout experience.
4
Listen for confirmation via webhooks
Set up a webhook subscription so FanBasis notifies your server the moment payment.succeeded fires. React automatically — grant access, send a welcome email, update your database.
↗ Putting it all together

Say you sell a $29/month Discord community. Here's the complete flow:

  1. Create one checkout sessiontype: "subscription", frequency_days: 30, amount_cents: 2900. FanBasis returns a payment_link.
  2. Share the link — drop it in an email campaign, embed it as a button on your landing page, or send it directly to interested buyers.
  3. Customer pays — FanBasis handles the checkout form, card processing, and receipt. You don't touch any of it.
  4. Your server receives payment.succeeded — verify the signature, pull the buyer's email from the payload, and call the Discord API to assign them the member role automatically.
  5. Renewals are handled for you — every 30 days FanBasis rebills the subscriber and fires another payment.succeeded. If a renewal fails, payment.failed fires so you can revoke access.

No manual work, no chasing payments — the entire lifecycle runs on webhooks.

Environments

FanBasis gives you two environments: one for live payments, and one for testing. Both work identically — the only difference is which API key you use.

🟢 Production
Use your live API key. Real charges will be made to real cards.
🔵 Sandbox (Test Mode)
Use your test API key. No real charges. Safe to experiment freely.

Test Card Numbers

Use these card numbers in sandbox mode to simulate different payment scenarios. These are standard test card numbers accepted by our payment processor — they do not charge real cards.

ℹ Sandbox cards only

These numbers work exclusively in sandbox mode. Using them in production will result in a payment failure. Always use your sandbox API key when testing.

Card BrandCard NumberExpiryCVV
Visa4242 4242 4242 4242Any future dateAny 3 digits
Mastercard5555 5555 5555 4444Any future dateAny 3 digits
Amex3782 822463 10005Any future dateAny 4 digits
Discover6011 1111 1111 1117Any future dateAny 3 digits

Merchant of Record

FanBasis acts as the Merchant of Record (MoR) for all transactions processed through our platform. This means we handle the legal and compliance side of payment processing — but you still run your business. Understanding where our responsibilities end and yours begin helps you get the most out of the platform.

What is a Merchant of Record?

A Merchant of Record is the legal entity that processes payments on behalf of sellers. As the MoR, FanBasis is the entity of record on each transaction — meaning we handle payment processing, compliance, and card network relationships so you don't have to.

While FanBasis handles the payment infrastructure, you are responsible for delivering your product, managing your customer relationships, and maintaining a healthy account. Think of it as a partnership: we handle the payment complexity, you focus on building and delivering great products.

🌍 Global Payment Acceptance
Accept payments from customers worldwide without registering your business in each country. FanBasis handles the legal and compliance layer for your sales.
🧾 Simplified Tax Compliance
FanBasis collects buyer address information at checkout and provides it in your transaction exports, giving you and your accountant the data needed for tax reporting. Sales tax determination remains the seller's responsibility.
🛡️ Dispute Support
Chargebacks flow into your Resolution Center where you review them and submit your evidence. FanBasis forwards your response to the card networks and shields you from direct card network monitoring programs like Visa VAMP. Maintaining a low dispute rate is your responsibility.
✅ Built-In Compliance
PCI DSS compliance, fraud detection, and payment routing are all handled by FanBasis — no additional certifications required from you.

MoR vs Payment Gateway

Many platforms use a Payment Gateway (PG) like Stripe or PayPal in "pass-through" mode — where they connect you directly to payment networks but leave you responsible for everything else. FanBasis takes a fundamentally different approach as a full Merchant of Record.

FanBasis (Merchant of Record) Payment Gateway (e.g., Stripe)
Tax data & reporting ✓ Buyer address collected & exportable Varies by provider
Chargeback management ✓ You submit evidence, FanBasis handles card network filing ✗ You manage entirely
Legal liability for transactions ✓ FanBasis assumes ✗ You assume
International sales ✓ No per-country registration required Requires local business registration per country
PCI DSS compliance ✓ Fully included Partial — you still have compliance obligations
Setup complexity ✓ Low — one API integration High — legal entity setup, fraud tools, compliance configuration
Fraud protection ✓ Included Add-on at extra cost
ℹ Your responsibilities as a seller

While FanBasis handles the payment infrastructure, you are responsible for: fulfilling orders and delivering what you promised, providing dispute evidence when chargebacks occur, and maintaining a healthy account by keeping refund and dispute rates low. FanBasis also requires compliance with our Merchant Acceptance Policy below — as the entity of record, we must ensure all sales meet our agreements and applicable regulations.

Payment Acceptance

FanBasis supports payment acceptance from customers internationally. Your customers can pay using major credit and debit cards, Apple Pay, Google Pay, and Cash App Pay.

⚠ Sanctioned regions

Payments from customers in sanctioned or restricted jurisdictions (such as those on OFAC sanctions lists) will be automatically declined. FanBasis handles this automatically — no configuration is required from you.

Merchant Acceptance Policy

FanBasis supports creators and businesses across a wide range of industries. However, to maintain platform integrity and comply with our payment processor agreements, certain business types and product categories are not permitted.

Please review this list before launching your product. If you're unsure, contact FanBasis support before going live.

✓ Permitted on FanBasis
  • Digital content & subscriptions (courses, newsletters, communities)
  • Creator memberships (Discord, Telegram, Slack groups)
  • Software & SaaS products
  • Digital downloads & files
  • Consulting & professional services
  • Online events & experiences
  • Coaching & mentorship programs
  • Paid newsletters & media
✗ Not Permitted on FanBasis
  • Adult / explicit content
  • Gambling, lottery, or wagering services
  • Cryptocurrency, forex, or investment products
  • Firearms, ammunition, or regulated weapons
  • Prescription medications or controlled substances
  • Multi-level marketing (MLM) or pyramid schemes
  • High-risk financial services
  • Counterfeit or pirated goods
⚠ Policy violations

Violating these policies may result in immediate account suspension and fund holds. If you're unsure whether your product qualifies, please contact support@fanbasis.com before launching.

Review & Monitoring Policy

As the Merchant of Record, FanBasis actively monitors all transactions on our platform to detect fraud, unusual activity, and policy violations. This protects both you and your customers — but keeping your account in good standing is a shared effort. Sellers with high dispute rates, excessive refunds, or policy violations may face account restrictions.

◆ Transaction Monitoring

All transactions are screened in real time for fraud signals using offer type, customer IP address, risk score, and other signals. Because FanBasis is the Merchant of Record, card network monitoring programs like Visa VAMP evaluate FanBasis at the platform level — not individual sellers — shielding your account from direct network-level scrutiny.

◆ Dispute Auto-Resolution (RDR/Ethoca)

FanBasis uses RDR and Ethoca to automatically resolve eligible low-value disputes before they escalate into chargebacks. This helps protect your dispute rate. For disputes that do escalate, they appear in your Resolution Center where you provide the evidence — FanBasis then files it with the card networks on your behalf. Responding to disputes promptly and with strong evidence is key to maintaining a healthy account.

◆ Account Reviews

New accounts may undergo a brief review period before payouts are enabled. Accounts suspected of policy violations may be placed on temporary hold pending investigation. We reserve the right to request business documentation — such as business registration or ID verification — for accounts processing above certain thresholds.

Avoiding App Store Fees

Creators who sell digital products or memberships directly via the web — rather than through in-app purchases inside an iOS or Android app — pay only FanBasis's processing fee and keep the rest, instead of giving Apple or Google 15–30% of every sale.

Where this applies

FanBasis checkout works for any sale completed outside of a native app storefront. Common use cases:

⚠ Native app restrictions

Apple and Google require that purchases made inside a native iOS or Android app go through their in-app purchase systems. You cannot link to or promote external payment options from within a native app. FanBasis checkout is not suitable as a replacement for in-app purchases within a live App Store or Google Play app. This restriction applies to the app itself — you can freely share FanBasis checkout links via email, your website, SMS, or social media. FanBasis is not responsible for ensuring your implementation complies with app store policies — consult Apple's and Google's current developer guidelines and a legal professional before implementation.

● Evolving regulations

Recent legal rulings (including the Epic v. Apple case) have opened new options in certain jurisdictions. The rules are actively changing — consult a legal professional if you need guidance specific to your situation.

Webhooks

Webhooks are the bridge between FanBasis and your application. Instead of constantly asking "did something happen?", your server just listens and FanBasis calls it automatically whenever an event occurs — like a payment coming in or a subscription being canceled.

⬡ How webhooks work

You give FanBasis a URL on your server (e.g., https://yoursite.com/webhooks). When an event happens, FanBasis makes an HTTP POST to that URL with a JSON payload describing the event. Your server reads the payload, verifies it's genuine using your secret key, and takes action.

Common Webhook Use Cases

When this event fires…You might want to…
payment.succeededGrant access, send a welcome email, update your database, generate a receipt.
payment.failedEmail the customer, flag the account, or retry the charge later.
subscription.createdCreate an account for the customer in your system, assign roles or permissions.
subscription.renewedLog the renewal, extend access dates in your database.
subscription.canceledRevoke access, downgrade the account, trigger a re-engagement campaign.
product.purchasedFulfill the order, email a download link, or unlock digital content.
refund.createdRevoke access, update your records, account for the non-refundable processing fee.
dispute.createdAlert your team immediately and begin gathering transaction evidence.
dispute.updatedCheck data.status — if won, restore access; if lost, deduct the amount and fee from your records.

All Event Types

EventWhat it means
payment.succeededA payment was successfully processed and funds are on their way to you.
payment.failedA payment attempt was declined or failed.
payment.expiredA checkout session expired before the customer completed payment.
payment.canceledA payment was canceled before it was completed.
product.purchasedA customer completed a one-time product purchase.
subscription.createdA customer started a new subscription.
subscription.renewedA subscription successfully billed for a new period.
subscription.completedA subscription ran through all its billing periods and ended.
subscription.canceledA subscription was canceled — either by you or by the customer.
subscription.payment_failedA recurring subscription charge failed.
refund.createdA refund was issued to a customer.
dispute.createdA chargeback was filed by the customer's bank.
dispute.updatedA dispute's status changed (e.g., won, lost, under_review).

Webhook Subscription Endpoints

Use these endpoints to manage which URLs receive your events and which event types they listen for.

List Your Webhook Subscriptions

GET /public-api/webhook-subscriptions

Shows all webhook endpoints you've registered, what events they're listening for, and whether they're active.

Request
curl https://www.fanbasis.com/public-api/webhook-subscriptions \
  -H "x-api-key: YOUR_API_KEY"
Response
{
  "status": "success",
  "data": [
    {
      "id": "ws_abc123",
      "webhook_url": "https://yoursite.com/webhooks",
      "event_types": ["payment.succeeded", "subscription.created"],
      "is_active": true,
      "created_at": "2025-01-01T00:00:00Z"
    }
  ]
}

Create a Webhook Subscription

POST /public-api/webhook-subscriptions

Registers a new URL to receive webhook events. You choose which events to subscribe to. The response includes a secret_key — use it to verify that incoming requests are genuinely from FanBasis.

⚠ Save your secret key now

The secret_key is only shown once. Store it securely (e.g., as an environment variable). You'll use it to validate the signature of every incoming webhook request.

Request Body
{
  "webhook_url": "https://yoursite.com/webhooks/fanbasis",
  "event_types": [
    "payment.succeeded",
    "payment.failed",
    "subscription.created",
    "subscription.canceled"
  ]
}
Request
curl -X POST https://www.fanbasis.com/public-api/webhook-subscriptions \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "webhook_url": "https://yourapp.com/webhooks/fanbasis",
    "event_types": ["payment.succeeded", "subscription.created", "subscription.canceled"]
  }'
Response
{
  "status": "success",
  "data": {
    "id": "ws_new123",
    "webhook_url": "https://yourapp.com/webhooks/fanbasis",
    "event_types": ["payment.succeeded", "subscription.created", "subscription.canceled"],
    "secret_key": "whsec_abcdef1234567890",
    "is_active": true,
    "created_at": "2025-01-15T10:00:00Z"
  }
}

Delete a Webhook Subscription

DELETE /public-api/webhook-subscriptions/:webhookSubscriptionId

Removes a webhook subscription. FanBasis will immediately stop sending events to that URL.

Path Parameters

ParameterTypeRequiredDescription
webhookSubscriptionIdstringYesThe ID of the webhook subscription to remove.
Request
curl -X DELETE "https://www.fanbasis.com/public-api/webhook-subscriptions/ws_abc123" \
  -H "x-api-key: YOUR_API_KEY"
Response
{
  "status": "success",
  "message": "Webhook subscription deleted successfully",
  "data": []
}

Test a Webhook Subscription

POST /public-api/webhook-subscriptions/:webhookSubscriptionId/test

Sends a simulated event to your webhook URL so you can verify everything is working before going live. Great for testing your server's response logic without needing a real payment.

Path Parameters

ParameterTypeRequiredDescription
webhookSubscriptionIdstringYesThe ID of the webhook subscription to test.
Request Body
{
  "event_type": "payment.succeeded"
}
Request
curl -X POST "https://www.fanbasis.com/public-api/webhook-subscriptions/ws_abc123/test" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "event_type": "payment.succeeded" }'
Response
{
  "status": "success",
  "message": "Test event sent successfully",
  "data": {
    "event_sent": true,
    "response_status": 200,
    "response_body": "OK"
  }
}

Webhook Security & Best Practices

Once you start receiving real payment events, these practices will save you from subtle, hard-to-debug issues:

⚠ Validate every incoming webhook

Anyone on the internet can POST to your webhook URL. Always verify that the request actually came from FanBasis. Each webhook includes a signature header — compare it against a hash of the request body using your webhook secret before trusting the payload.

✦ Return 200 immediately — process async

FanBasis expects your endpoint to respond with HTTP 200 within a few seconds. If your logic takes longer (e.g., sending emails, updating a database), respond with 200 first and process the work asynchronously in a background job. This prevents timeouts and duplicate deliveries.

ℹ Handle duplicate events gracefully

Webhooks can occasionally be delivered more than once (e.g., network retry after a timeout). Make your webhook handler idempotent — check whether you've already processed an event by its ID before taking action a second time.

● Troubleshooting webhook issues

Events not arriving? Make sure your webhook URL is publicly accessible (not localhost), your server returns HTTP 200, and no firewall rules are blocking FanBasis's requests. Use the Test Webhook endpoint to verify connectivity before going live. Signature mismatch? Double-check that you're using the webhook secret for the correct subscription and that you're hashing the raw request body — not a re-serialized JSON string.

🔐 Signature Validation

All webhook requests are signed using HMAC-SHA256 to guarantee payload authenticity. The signature is sent in the x-webhook-signature header, computed from your webhook secret key and the raw request body. Always validate this before processing any event.

⚠ Always use the raw request body

Never re-serialize the parsed JSON to generate or compare signatures — JSON serializers can reorder keys or alter whitespace, causing a mismatch even on legitimate requests. Read the raw bytes exactly as received off the wire.

How validation works

  1. Capture the raw request body before any parsing
  2. Read the signature from the x-webhook-signature header
  3. Compute HMAC-SHA256(raw_body, your_secret) and hex-encode it
  4. Compare using a constant-time comparison to prevent timing attacks
  5. Reject with 401 Unauthorized if signatures do not match
PHP
// Validate FanBasis webhook signature in PHP
$payload   = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_FANBASIS_SIGNATURE'] ?? '';
$secret    = 'your_webhook_secret_key';

$expected = hash_hmac('sha256', $payload, $secret);

if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    exit('Invalid signature');
}

// Safe to process the event
$event = json_decode($payload, true);
error_log('Received event: ' . $event['type']);

Security best practices

✦ Use constant-time comparison

Standard string equality short-circuits on the first differing byte, leaking timing information. Use hash_equals / timingSafeEqual / hmac.compare_digest instead.

⚠ Guard your secret key

Store it in an environment variable or secrets manager — never hard-code it or commit it to source control. Rotate immediately if it is ever exposed.

ℹ Log failed validations

A spike in signature failures could indicate someone probing your endpoint. Log the source IP and timestamp for every rejected request.

● Return 401, not 400

Use HTTP 401 Unauthorized for signature failures — this signals an authentication problem, not a bad request, making it easier to triage in logs and monitoring.

Webhook Events Reference

Every action in FanBasis — a payment coming in, a subscription renewing, a dispute being filed — fires a webhook event to your registered endpoint. This reference documents every event type, exactly what fields to expect, and what each field means so you can build reliable integrations with confidence.

⬡ How to read this reference

Most events send a flat JSON payload directly to your endpoint. Dispute and refund events use an envelope format with a top-level id, type, and a data object containing the actual event data. Each event below shows its full payload shape and a field-by-field breakdown. Use the type field to route events to the right handler in your code.

✦ Quick handler pattern

The simplest webhook handler: (1) verify the signature, (2) read the type field, (3) switch to the right handler, (4) return 200. Don't run heavy logic synchronously — push work to a queue and respond fast.

All 12 Events at a Glance

EventCategoryWhen it firesFormat
payment.succeededPaymentA payment was successfully chargedFlat
payment.failedPaymentA payment attempt was declined or erroredFlat
payment.expiredPaymentA checkout session timed out before paymentFlat
payment.canceledPaymentA payment was canceled before completionFlat
product.purchasedProductA one-time product purchase was completedFlat
subscription.createdSubscriptionA new subscription was started (first payment)Flat
subscription.renewedSubscriptionA subscription successfully billed for another periodFlat
subscription.completedSubscriptionA subscription finished all scheduled paymentsFlat
subscription.canceledSubscriptionA subscription was canceledFlat
dispute.createdDisputeA chargeback was filed by the customer's bankEnvelope
dispute.updatedDisputeA dispute's status changed (e.g., resolved as won)Envelope
refund.createdRefundA refund was issued to a customerEnvelope

Payment Events payment.succeededpayment.failedpayment.expiredpayment.canceled

These events track the lifecycle of individual payment attempts. Every checkout — one-time or subscription — produces at least one of these events.

payment.succeeded Fires when a charge is successfully processed

This is the most important event. It tells you a customer paid and money is on its way to you. Use it to grant access, send a receipt, record the transaction, and trigger any post-purchase fulfilment. Both one-time payments and the initial payment of a subscription produce this event.

✦ What to do when this fires

Check item.type to distinguish between a subscription first-payment and a one-time charge. For subscriptions, also listen for subscription.created which carries additional metadata. Use payment_id as your idempotency key — store it to prevent granting access twice if the event delivers more than once.

Example Payload
{
  "payment_id": "txn_abc123",
  "checkout_session_id": 345424,
  "customer_id": "cust_def456",
  "subscription_id": "sub_ghi012",
  "buyer": {
    "id": 12345,
    "email": "alex@example.com",
    "name": "Alex Johnson",
    "country_code": "US",
    "ip_address": "203.0.113.42"
  },
  "item": {
    "id": 678,
    "name": "Pro Membership",
    "type": "subscription",
    "description": "Monthly access to all premium content",
    "image": "https://cdn.example.com/pro-icon.png",
    "quantity": 1,
    "unit_price": 2900,
    "tax": 0
  },
  "amount": 2900,
  "currency": "USD",
  "status": "paid",
  "payment_method": "card",
  "api_metadata": {
    "discord_user_id": "123456789",
    "plan": "monthly"
  }
}

Field Reference

FieldTypeNotes
payment_idstringUnique transaction ID. Use as your idempotency key. May be null for free-trial subscriptions.
checkout_session_idintegerThe numeric checkout session ID that generated this payment.
customer_idstringThe FanBasis customer record for the buyer.
subscription_idstring|nullSet for subscription payments. Null for one-time purchases.
buyer.idintegerThe buyer's unique user ID.
buyer.emailstringThe buyer's email address.
buyer.namestringFull name as entered at checkout.
buyer.country_codestringISO 3166-1 alpha-2 code (e.g. "US", "GB").
buyer.ip_addressstringBuyer IP at time of purchase.
item.idintegerThe purchased item's unique ID.
item.typestringOne of: subscription, one_time, single_payment, payment_link_subscription.
item.unit_priceintegerPrice in smallest currency unit (cents). 2900 = $29.00.
amountintegerTotal amount charged, in cents.
currencystringISO 4217 code (e.g. "USD").
statusstringAlways paid for this event.
payment_methodstringPayment method used (e.g. card).
api_metadataobject|nullKey-value pairs you passed when creating the checkout session. Use to pass Discord IDs, plan names, or other context into your webhook handler.

Schema

JSON Schema
{
  "type": "object",
  "required": ["payment_id", "amount", "currency", "status", "created_at", "buyer", "item"],
  "properties": {
    "payment_id": { "type": "string", "description": "The unique identifier for the payment" },
    "amount": { "type": "number", "description": "The payment amount" },
    "currency": { "type": "string", "description": "The payment currency (e.g., USD)" },
    "status": { "type": "string", "description": "The payment status (e.g., succeeded)" },
    "created_at": { "type": "string", "format": "date-time", "description": "ISO 8601 timestamp" },
    "buyer": {
      "type": "object",
      "required": ["id", "name", "email"],
      "properties": {
        "id": { "type": "integer", "description": "The buyer's user ID" },
        "name": { "type": "string", "description": "The buyer's full name" },
        "email": { "type": "string", "format": "email", "description": "The buyer's email address" }
      }
    },
    "item": {
      "type": "object",
      "required": ["id", "title", "type"],
      "properties": {
        "id": { "type": "integer", "description": "The purchased item's ID" },
        "title": { "type": "string", "description": "The purchased item's title" },
        "type": { "type": "string", "description": "The type of item (e.g., subscription)" }
      }
    },
    "api_metadata": { "type": "object", "description": "Additional metadata for API integration" }
  }
}

Example Payload

Example JSON
{
  "payment_id": "pi_3RKxLmGb...",
  "amount": 49.99,
  "currency": "USD",
  "status": "succeeded",
  "created_at": "2025-11-20T21:42:47+00:00",
  "buyer": {
    "id": 8421,
    "name": "Jane Cooper",
    "email": "jane@example.com"
  },
  "item": {
    "id": 2150,
    "title": "Pro Membership",
    "type": "subscription"
  }
}
payment.failed Fires when a payment attempt is declined or errors

Sent when a charge attempt fails — expired card, insufficient funds, or a bank decline. For subscriptions, FanBasis may retry the charge automatically and you'll receive this event for each failed attempt. Check failure_reason to understand why it failed and consider sending the customer a payment-update email.

Example Payload
{
  "payment_id": "txn_abc123",
  "checkout_session_id": 345424,
  "customer_id": "cust_def456",
  "subscription_id": "sub_ghi012",
  "failure_reason": "card_declined",
  "status_code": 402,
  "status": "failed"
}

Field Reference

FieldTypeNotes
payment_idstringThe failed transaction attempt ID.
customer_idstringThe customer who attempted to pay.
subscription_idstring|nullSet if this is a failed subscription renewal attempt.
failure_reasonstringMachine-readable reason: card_declined, insufficient_funds, expired_card, etc.
status_codeintegerHTTP-style status code from the payment processor.
statusstringAlways failed for this event.

Schema

JSON Schema
{
  "type": "object",
  "required": ["payment_id", "customer_id", "failure_reason", "timestamp"],
  "properties": {
    "payment_id": { "type": "string", "description": "The unique identifier for the payment" },
    "customer_id": { "type": "integer", "description": "The customer/user ID. May be 0 if undetermined." },
    "subscription_id": { "type": ["integer", "null"], "description": "The subscription ID if related" },
    "failure_reason": { "type": "string", "description": "The reason for the payment failure" },
    "status_code": { "type": ["string", "null"], "description": "Gateway status code (e.g., card_declined)" },
    "timestamp": { "type": "string", "format": "date-time", "description": "ISO 8601 timestamp" },
    "service_id": { "type": ["integer", "null"], "description": "The service/product ID if available" },
    "event_type": { "type": "string", "description": "The event type identifier" }
  }
}

Example Payload

Example JSON
{
  "payment_id": "pi_3RLaB2Gb...",
  "customer_id": 8421,
  "failure_reason": "Your card was declined",
  "timestamp": "2025-11-20T21:42:47+00:00"
}
payment.expired Fires when a checkout session times out before payment

A checkout session was created but the customer never completed payment before the session expired. Note that customer_id and subscription_id are null — the customer never completed the payment flow. You can use this event to trigger a re-engagement flow ("You left something behind!") if you have the customer's contact from a prior interaction.

Example Payload
{
  "checkout_session_id": 345424,
  "customer_id": null,
  "subscription_id": null,
  "expiration_date": "2025-01-08T14:30:00.000Z",
  "item": {
    "id": 678,
    "name": "Pro Membership",
    "type": "subscription",
    "description": "Monthly access to all premium content",
    "image": null,
    "quantity": 1,
    "unit_price": 2900,
    "tax": 0
  }
}

Field Reference

FieldTypeNotes
checkout_session_idintegerThe numeric session ID that expired.
customer_idnullAlways null — no customer was created since payment never completed.
subscription_idnullAlways null — no subscription was created.
expiration_datestring (ISO 8601)UTC timestamp when the session expired.
itemobjectThe product from the expired session — tells you which product the customer was trying to buy.

Schema

JSON Schema
{
  "type": "object",
  "required": ["checkout_session_id", "customer_id", "failure_reason", "expiration_date", "timestamp"],
  "properties": {
    "checkout_session_id": { "type": "string", "description": "The checkout session ID that expired" },
    "customer_id": { "type": "integer", "description": "The customer/user ID. May be 0 if undetermined." },
    "subscription_id": { "type": ["integer", "null"], "description": "The subscription ID if related" },
    "failure_reason": { "type": "string", "description": "Always 'Payment session expired'" },
    "expiration_date": { "type": "string", "format": "date-time", "description": "When the session expired" },
    "timestamp": { "type": "string", "format": "date-time", "description": "When the event was processed" },
    "service_id": { "type": ["integer", "null"], "description": "The service/product ID" },
    "service_title": { "type": ["string", "null"], "description": "The service/product title" },
    "service_type": { "type": ["string", "null"], "description": "The service type (e.g., subscription, onetime)" },
    "event_type": { "type": "string", "description": "The event type identifier" }
  }
}

Example Payload

Example JSON
{
  "checkout_session_id": "cs_9f2a3b...",
  "customer_id": 8421,
  "failure_reason": "Payment session expired",
  "expiration_date": "2025-11-20T21:42:47+00:00",
  "timestamp": "2025-11-20T21:42:47+00:00"
}
payment.canceled Fires when a payment is explicitly canceled

Sent when a payment is actively canceled — either the customer clicked cancel or you canceled it programmatically via the API. Unlike payment.expired, a customer_id is present here because the customer had begun the checkout flow. The failure_reason tells you who or what triggered the cancellation.

Example Payload
{
  "payment_id": "txn_abc123",
  "customer_id": "cust_def456",
  "failure_reason": "customer_canceled",
  "status": "canceled"
}

Field Reference

FieldTypeNotes
payment_idstringThe canceled transaction ID.
customer_idstringThe customer who was in the checkout flow.
failure_reasonstringWhy it was canceled. Common values: customer_canceled, api_canceled.
statusstringAlways canceled for this event.

Schema

JSON Schema
{
  "type": "object",
  "required": ["payment_id", "customer_id", "failure_reason", "timestamp"],
  "properties": {
    "payment_id": { "type": "string", "description": "The unique identifier for the canceled payment" },
    "customer_id": { "type": "integer", "description": "The customer/user ID. May be 0 if undetermined." },
    "subscription_id": { "type": ["integer", "null"], "description": "The subscription ID if related" },
    "failure_reason": { "type": "string", "description": "The reason for payment cancellation" },
    "timestamp": { "type": "string", "format": "date-time", "description": "ISO 8601 timestamp" },
    "service_id": { "type": ["integer", "null"], "description": "The service/product ID if available" },
    "event_type": { "type": "string", "description": "The event type identifier" }
  }
}

Example Payload

Example JSON
{
  "payment_id": "pi_3RMnC7Gb...",
  "customer_id": 8421,
  "failure_reason": "Payment was canceled by user",
  "timestamp": "2025-11-20T21:42:47+00:00"
}

Product Events product.purchased

Fires for one-time product purchases. Complements payment.succeeded with richer product context including upsell and order-bump attribution.

product.purchased Fires when a one-time product purchase completes

Sent specifically for one-time product purchases — digital goods, course access, lifetime memberships, etc. This event includes additional_params which tells you whether the purchase came through an order bump, upsell, or downsell. Use these flags to attribute revenue and understand your sales funnel. This event fires in addition to payment.succeeded.

ℹ Bump, upsell, and downsell explained

bump: customer added an order bump (add-on shown at checkout). upsell: purchase came from a post-checkout upsell page. downsell: customer took a lower-priced alternative after declining an upsell.

Example Payload
{
  "payment_id": "txn_abc123",
  "product_price": 2900,
  "buyer": {
    "id": 12346,
    "email": "jane@example.com",
    "name": "Jane Smith",
    "country_code": "US",
    "ip_address": "198.51.100.14"
  },
  "item": {
    "id": 679,
    "name": "Lifetime Access Pack",
    "type": "one_time",
    "description": "Lifetime access to all premium resources",
    "image": null,
    "quantity": 1,
    "unit_price": 2900,
    "tax": 0
  },
  "additional_params": {
    "bump": false,
    "upsell": true,
    "downsell": false
  }
}

Field Reference

FieldTypeNotes
payment_idstringThe transaction ID for this purchase.
product_priceintegerPrice in cents. 2900 = $29.00.
buyerobjectBuyer identity: id, email, name, country_code, ip_address.
buyer.idintegerThe buyer's unique user ID.
itemobjectProduct details: id, name, type, description, quantity, unit_price, tax.
item.idintegerThe purchased item's unique ID.
additional_params.bumpbooleanTrue if this purchase originated from an order bump.
additional_params.upsellbooleanTrue if this purchase originated from an upsell offer.
additional_params.downsellbooleanTrue if this purchase originated from a downsell offer.

Schema

JSON Schema
{
  "type": "object",
  "required": ["payment_id", "currency", "status", "created_at", "product_price", "buyer", "item"],
  "properties": {
    "payment_id": { "type": "string", "description": "The unique identifier for the payment" },
    "currency": { "type": "string", "description": "The payment currency (e.g., USD)" },
    "status": { "type": "string", "description": "The payment status (e.g., succeeded)" },
    "created_at": { "type": "string", "format": "date-time", "description": "ISO 8601 timestamp" },
    "product_price": { "type": "number", "description": "The price of the purchased product" },
    "buyer": {
      "type": "object",
      "required": ["id", "name", "email"],
      "properties": {
        "id": { "type": "integer" }, "name": { "type": "string" }, "email": { "type": "string" }
      }
    },
    "item": {
      "type": "object",
      "required": ["id", "title", "type"],
      "properties": {
        "id": { "type": "integer" }, "title": { "type": "string" }, "type": { "type": "string" }
      }
    },
    "api_metadata": { "type": "object", "description": "Additional metadata for API integration" },
    "additional_params": { "type": ["object", "null"], "description": "Additional purchase parameters (upsell, downsell, bump)" },
    "event_type": { "type": "string", "description": "The event type identifier" }
  }
}

Example Payload

Example JSON
{
  "payment_id": "pi_3RNpD9Gb...",
  "currency": "USD",
  "status": "succeeded",
  "created_at": "2025-11-20T21:42:47+00:00",
  "product_price": 29.99,
  "buyer": {
    "id": 8421,
    "name": "Jane Cooper",
    "email": "jane@example.com"
  },
  "item": {
    "id": 3042,
    "title": "Digital Marketing Course",
    "type": "onetime"
  }
}

Subscription Events subscription.createdsubscription.renewedsubscription.completedsubscription.canceled

These four events cover the complete subscription lifecycle. Together they let you keep your own access system perfectly in sync with FanBasis's billing state.

subscription.created Fires when a new subscription is started (first payment)

The customer just started a subscription — this is their first successful charge. The payload includes a subscription object with the subscription's ID, status, billing frequency, and start date. Use this to create an account, assign a role or plan tier, and set an access-expiry date in your database.

ℹ Free trial handling

When subscription.is_free_trial is true, no money was collected yet. This event still fires so you can grant access immediately. The first real charge will produce a subscription.renewed event when the trial ends.

Example Payload
{
  "payment_id": "txn_abc123",
  "checkout_session_id": 345424,
  "customer_id": "cust_def456",
  "subscription_id": "sub_ghi012",
  "buyer": {
    "id": 12345,
    "email": "alex@example.com",
    "name": "Alex Johnson",
    "country_code": "US",
    "ip_address": "203.0.113.42"
  },
  "item": {
    "id": 678,
    "name": "Pro Membership",
    "type": "subscription",
    "description": "Monthly access to all premium content",
    "image": null,
    "quantity": 1,
    "unit_price": 2900,
    "tax": 0
  },
  "amount": 2900,
  "currency": "USD",
  "status": "paid",
  "payment_method": "card",
  "subscription": {
    "id": "sub_ghi012",
    "status": "active",
    "start_date": "2025-01-15T10:00:00.000Z",
    "is_free_trial": false,
    "payment_frequency": "monthly"
  },
  "api_metadata": {
    "discord_user_id": "123456789"
  }
}

Subscription Object Fields

FieldTypeNotes
subscription.idstringUnique subscription ID. Store this to link future subscription events.
subscription.statusstringactive for a live subscription.
subscription.start_datestring (ISO 8601)When the subscription became active.
subscription.is_free_trialbooleanTrue if this period is a free trial with no charge yet.
subscription.payment_frequencystringBilling cadence: daily, weekly, monthly, yearly, or a custom interval.

Schema

JSON Schema
{
  "type": "object",
  "required": ["payment_id", "amount", "currency", "status", "created_at", "buyer", "item", "subscription"],
  "properties": {
    "payment_id": { "type": "string" },
    "amount": { "type": "number" },
    "currency": { "type": "string" },
    "status": { "type": "string" },
    "created_at": { "type": "string", "format": "date-time" },
    "buyer": {
      "type": "object", "required": ["id", "name", "email"],
      "properties": { "id": { "type": "integer" }, "name": { "type": "string" }, "email": { "type": "string" } }
    },
    "item": {
      "type": "object", "required": ["id", "title", "type"],
      "properties": { "id": { "type": "integer" }, "title": { "type": "string" }, "type": { "type": "string" } }
    },
    "subscription": {
      "type": "object", "required": ["id", "status", "start_date"],
      "properties": {
        "id": { "type": "integer", "description": "The subscription's unique identifier" },
        "status": { "type": "string", "description": "The subscription status (e.g., active)" },
        "start_date": { "type": "string", "format": "date-time" },
        "end_date": { "type": ["string", "null"], "format": "date-time" },
        "is_free_trial": { "type": "boolean", "description": "Whether this is a free trial" },
        "payment_frequency": { "type": "string", "description": "e.g., monthly" }
      }
    },
    "api_metadata": { "type": "object" }
  }
}

Example Payload

Example JSON
{
  "payment_id": "pi_3ROqE1Gb...",
  "amount": 19.99,
  "currency": "USD",
  "status": "succeeded",
  "created_at": "2025-11-20T21:42:47+00:00",
  "buyer": {
    "id": 8421,
    "name": "Jane Cooper",
    "email": "jane@example.com"
  },
  "item": {
    "id": 2150,
    "title": "Pro Membership",
    "type": "subscription"
  },
  "subscription": {
    "id": 5012,
    "status": "active",
    "start_date": "2025-11-20T21:42:47+00:00"
  }
}
subscription.renewed Fires on each successful recurring charge

A subscription successfully billed for another period. Nearly identical to subscription.created but the subscription object adds renewed_at and auto_renew_count. Use this to extend access dates and log recurring revenue in your system.

Example Payload
{
  "payment_id": "txn_ren456",
  "checkout_session_id": 345424,
  "customer_id": "cust_def456",
  "subscription_id": "sub_ghi012",
  "buyer": {
    "id": 12345,
    "email": "alex@example.com",
    "name": "Alex Johnson",
    "country_code": "US",
    "ip_address": "203.0.113.42"
  },
  "item": {
    "id": 678,
    "name": "Pro Membership",
    "type": "subscription",
    "description": "Monthly access to all premium content",
    "image": null,
    "quantity": 1,
    "unit_price": 2900,
    "tax": 0
  },
  "amount": 2900,
  "currency": "USD",
  "status": "paid",
  "payment_method": "card",
  "subscription": {
    "id": "sub_ghi012",
    "status": "active",
    "start_date": "2025-01-15T10:00:00.000Z",
    "is_free_trial": false,
    "payment_frequency": "monthly",
    "renewed_at": "2025-02-15T10:00:00.000Z",
    "auto_renew_count": 1
  },
  "api_metadata": {
    "discord_user_id": "123456789"
  }
}

Additional Fields (vs. subscription.created)

FieldTypeNotes
subscription.renewed_atstring (ISO 8601)Timestamp of this specific renewal.
subscription.auto_renew_countintegerHow many times this subscription has auto-renewed. Starts at 1 after the first renewal.

Schema

JSON Schema
{
  "type": "object",
  "required": ["payment_id", "amount", "currency", "status", "created_at", "buyer", "item", "subscription"],
  "properties": {
    "payment_id": { "type": "string" },
    "amount": { "type": "number" },
    "currency": { "type": "string" },
    "status": { "type": "string" },
    "created_at": { "type": "string", "format": "date-time" },
    "buyer": {
      "type": "object", "required": ["id", "name", "email"],
      "properties": { "id": { "type": "integer" }, "name": { "type": "string" }, "email": { "type": "string" } }
    },
    "item": {
      "type": "object", "required": ["id", "title", "type"],
      "properties": { "id": { "type": "integer" }, "title": { "type": "string" }, "type": { "type": "string" } }
    },
    "subscription": {
      "type": "object", "required": ["id", "status", "start_date", "renewed_at"],
      "properties": {
        "id": { "type": "integer" },
        "status": { "type": "string" },
        "start_date": { "type": "string", "format": "date-time" },
        "renewed_at": { "type": "string", "format": "date-time", "description": "When the subscription was renewed" },
        "end_date": { "type": ["string", "null"] },
        "payment_frequency": { "type": "string" },
        "auto_renew_count": { "type": "integer", "description": "Number of auto-renewals" }
      }
    },
    "api_metadata": { "type": "object" }
  }
}

Example Payload

Example JSON
{
  "payment_id": "pi_3RPrF2Gb...",
  "amount": 19.99,
  "currency": "USD",
  "status": "succeeded",
  "created_at": "2025-12-20T21:42:47+00:00",
  "buyer": {
    "id": 8421,
    "name": "Jane Cooper",
    "email": "jane@example.com"
  },
  "item": {
    "id": 2150,
    "title": "Pro Membership",
    "type": "subscription"
  },
  "subscription": {
    "id": 5012,
    "status": "active",
    "start_date": "2025-11-20T21:42:47+00:00",
    "renewed_at": "2025-12-20T21:42:47+00:00"
  }
}
subscription.completed Fires when a subscription finishes all scheduled payments

The subscription ran through all its scheduled payments and has naturally completed. This differs from subscription.canceled in that no one canceled it — it simply reached its intended end. Check completion_reason and completed_at to know why and when. Use this to gracefully downgrade the customer's access or start an offboarding flow.

Example Payload
{
  "payment_id": "txn_abc123",
  "checkout_session_id": 345424,
  "customer_id": "cust_def456",
  "subscription_id": "sub_ghi012",
  "buyer": {
    "id": 12345,
    "email": "alex@example.com",
    "name": "Alex Johnson",
    "country_code": "US",
    "ip_address": "203.0.113.42"
  },
  "item": {
    "id": 680,
    "name": "6-Month Coaching Program",
    "type": "subscription",
    "description": "6 monthly payments",
    "image": null,
    "quantity": 1,
    "unit_price": 4900,
    "tax": 0
  },
  "amount": 4900,
  "currency": "USD",
  "status": "paid",
  "payment_method": "card",
  "subscription": {
    "id": "sub_ghi012",
    "status": "completed",
    "start_date": "2024-07-01T00:00:00.000Z",
    "is_free_trial": false,
    "payment_frequency": "monthly",
    "completion_reason": "all_payments_completed",
    "completed_at": "2025-01-01T00:00:00.000Z"
  }
}

Additional Fields (vs. subscription.created)

FieldTypeNotes
subscription.statusstringcompleted for this event.
subscription.completion_reasonstringWhy it completed (e.g. all_payments_completed).
subscription.completed_atstring (ISO 8601)When the subscription completed.

Schema

JSON Schema
{
  "type": "object",
  "required": ["buyer", "item", "subscription"],
  "properties": {
    "buyer": {
      "type": "object", "required": ["id", "name", "email"],
      "properties": { "id": { "type": "integer" }, "name": { "type": "string" }, "email": { "type": "string" } }
    },
    "item": {
      "type": "object", "required": ["id", "title", "type"],
      "properties": { "id": { "type": "integer" }, "title": { "type": "string" }, "type": { "type": "string" } }
    },
    "subscription": {
      "type": "object", "required": ["id", "status", "start_date", "completed_at"],
      "properties": {
        "id": { "type": "integer" },
        "status": { "type": "string", "description": "e.g., completed" },
        "start_date": { "type": "string", "format": "date-time" },
        "completed_at": { "type": "string", "format": "date-time" },
        "payment_frequency": { "type": "string" },
        "auto_renew_count": { "type": "integer" },
        "completion_reason": { "type": "string", "description": "e.g., period_ended, cancelled" }
      }
    },
    "api_metadata": { "type": "object" }
  }
}

Example Payload

Example JSON
{
  "buyer": {
    "id": 8421,
    "name": "Jane Cooper",
    "email": "jane@example.com"
  },
  "item": {
    "id": 2150,
    "title": "Pro Membership",
    "type": "subscription"
  },
  "subscription": {
    "id": 5012,
    "status": "completed",
    "start_date": "2025-11-20T21:42:47+00:00",
    "completed_at": "2026-11-20T21:42:47+00:00"
  }
}
subscription.canceled Fires when a subscription is actively canceled

A subscription was canceled — by the customer requesting cancellation, by you calling the Cancel Subscription API, or by FanBasis after too many failed payment retries. Check cancellation_reason to distinguish between these cases. Use this event to revoke access, trigger a win-back campaign, and update the subscription status in your database.

Example Payload
{
  "payment_id": "txn_abc123",
  "checkout_session_id": 345424,
  "customer_id": "cust_def456",
  "subscription_id": "sub_ghi012",
  "buyer": {
    "id": 12345,
    "email": "alex@example.com",
    "name": "Alex Johnson",
    "country_code": "US",
    "ip_address": "203.0.113.42"
  },
  "item": {
    "id": 678,
    "name": "Pro Membership",
    "type": "subscription",
    "description": "Monthly access to all premium content",
    "image": null,
    "quantity": 1,
    "unit_price": 2900,
    "tax": 0
  },
  "amount": 2900,
  "currency": "USD",
  "status": "canceled",
  "subscription": {
    "id": "sub_ghi012",
    "status": "canceled",
    "start_date": "2025-01-15T10:00:00.000Z",
    "is_free_trial": false,
    "payment_frequency": "monthly",
    "cancelled_at": "2025-03-20T14:22:00.000Z",
    "cancellation_reason": "customer_request"
  }
}

Additional Fields (vs. subscription.created)

FieldTypeNotes
subscription.statusstringcanceled for this event.
subscription.cancelled_atstring (ISO 8601)When the subscription was canceled.
subscription.cancellation_reasonstringCommon values: customer_request, api_request, payment_failed.

Schema

JSON Schema
{
  "type": "object",
  "required": ["buyer", "item", "subscription"],
  "properties": {
    "buyer": {
      "type": "object", "required": ["id", "name", "email"],
      "properties": { "id": { "type": "integer" }, "name": { "type": "string" }, "email": { "type": "string" } }
    },
    "item": {
      "type": "object", "required": ["id", "title", "type"],
      "properties": { "id": { "type": "integer" }, "title": { "type": "string" }, "type": { "type": "string" } }
    },
    "subscription": {
      "type": "object", "required": ["id", "status", "start_date", "cancelled_at"],
      "properties": {
        "id": { "type": "integer" },
        "status": { "type": "string", "description": "e.g., cancelled" },
        "start_date": { "type": "string", "format": "date-time" },
        "cancelled_at": { "type": "string", "format": "date-time" },
        "end_date": { "type": ["string", "null"] },
        "payment_frequency": { "type": "string" },
        "auto_renew_count": { "type": "integer" },
        "cancellation_reason": { "type": "string", "description": "e.g., user_request, admin_action" }
      }
    },
    "api_metadata": { "type": "object" }
  }
}

Example Payload

Example JSON
{
  "buyer": {
    "id": 8421,
    "name": "Jane Cooper",
    "email": "jane@example.com"
  },
  "item": {
    "id": 2150,
    "title": "Pro Membership",
    "type": "subscription"
  },
  "subscription": {
    "id": 5012,
    "status": "cancelled",
    "start_date": "2025-11-20T21:42:47+00:00",
    "cancelled_at": "2026-03-15T14:30:00+00:00"
  }
}

Dispute Events dispute.createddispute.updated

Dispute events use an envelope format — the actual data lives inside a data object, with top-level id, type, and created_at. Disputes are chargebacks filed by a customer's bank and must be responded to within the deadline shown in due_by.

dispute.created Fires the moment a chargeback is filed

A customer's bank has initiated a chargeback against one of your payments. This is time-sensitive — you typically have a narrow response window (see data.due_by) to submit evidence. Act on this event immediately by alerting your team and gathering evidence of the original transaction.

⚠ Respond before the deadline

Check data.due_by immediately. If you miss the response deadline, the dispute is automatically decided in the customer's favor. Submit your evidence through the FanBasis dashboard before the due_by date.

Example Payload (Envelope Format)
{
  "id": "fe3505d5-1b32-4c04-95bf-5d5f60957b7f",
  "type": "dispute.created",
  "data": {
    "id": 123,
    "dispute_id": "dp_1Q2w3E4r5T",
    "amount": 75.50,
    "dispute_fee": 15.00,
    "total_amount": 90.50,
    "status": "needs_response",
    "reason": "fraudulent",
    "payment_intent_id": "pi_3Nc8QJ2eZvKYlo2C0xYzAbCd",
    "due_by": "2025-02-08T23:59:59Z",
    "created_at": "2025-02-01T12:00:00Z",
    "updated_at": "2025-02-01T12:00:00Z",
    "creator_id": 456,
    "buyer": {
      "id": 789,
      "name": "John Doe",
      "email": "buyer@example.com"
    },
    "item": {
      "id": 321,
      "title": "Pro Membership",
      "type": "onetime"
    },
    "event_type": "dispute.created"
  },
  "created_at": "2025-02-01T12:00:00Z"
}

Field Reference

FieldTypeNotes
idstring (UUID)Unique event envelope ID — use for idempotency.
typestringAlways dispute.created.
created_atstring (ISO 8601)When the event was generated.
data.idintegerInternal FanBasis dispute record ID.
data.dispute_idstringProcessor-level dispute ID (dp_ prefix). Track this to correlate dispute.updated events.
data.amountnumberDisputed payment amount in dollars.
data.dispute_feenumberChargeback fee from the payment processor in dollars (typically $15).
data.total_amountnumberTotal financial exposure: amount + dispute_fee.
data.statusstringneeds_response when first created.
data.reasonstringBank-provided reason: fraudulent, product_not_received, duplicate, subscription_canceled, etc.
data.payment_intent_idstringProcessor payment intent ID for the disputed charge.
data.due_bystring (ISO 8601)Deadline to submit your response. Do not miss this.
data.creator_idintegerYour FanBasis creator/seller account ID.
data.buyerobjectDisputing customer: id, name, email.
data.itemobjectThe disputed product: id, title, type.

Schema

JSON Schema
{
  "type": "object",
  "required": ["id", "type", "data", "created_at"],
  "properties": {
    "id": { "type": "string", "description": "Unique event identifier (UUID)" },
    "type": { "type": "string", "description": "Always 'dispute.created'" },
    "data": {
      "type": "object",
      "required": ["id", "dispute_id", "amount", "status", "reason", "payment_intent_id", "due_by"],
      "properties": {
        "id": { "type": "integer" },
        "dispute_id": { "type": "string", "description": "The dispute identifier" },
        "amount": { "type": "number", "description": "The disputed amount" },
        "dispute_fee": { "type": "number", "description": "Fee charged for the dispute" },
        "total_amount": { "type": "number", "description": "Amount + dispute fee" },
        "status": { "type": "string", "description": "e.g., needs_response" },
        "reason": { "type": "string", "description": "e.g., fraudulent" },
        "payment_intent_id": { "type": "string" },
        "due_by": { "type": "string", "format": "date-time", "description": "Deadline to respond" },
        "created_at": { "type": "string", "format": "date-time" },
        "updated_at": { "type": "string", "format": "date-time" },
        "creator_id": { "type": "integer" },
        "buyer": { "type": "object", "properties": { "id": { "type": "integer" }, "name": { "type": "string" }, "email": { "type": "string" } } },
        "item": { "type": "object", "properties": { "id": { "type": "integer" }, "title": { "type": "string" }, "type": { "type": "string" } } },
        "event_type": { "type": "string" }
      }
    },
    "created_at": { "type": "string", "format": "date-time" }
  }
}

Example Payload

Example JSON
{
  "id": "fe3505d5-1b32-4c04-95bf-5d5f60957b7f",
  "type": "dispute.created",
  "data": {
    "id": 123,
    "dispute_id": "dp_1Q2w3E4r5T",
    "amount": 75.50,
    "dispute_fee": 15.00,
    "total_amount": 90.50,
    "status": "needs_response",
    "reason": "fraudulent",
    "payment_intent_id": "pi_3Nc8QJ2eZvKYlo2C0xYzAbCd",
    "due_by": "2026-03-01T23:59:59Z",
    "created_at": "2026-02-17T15:32:10Z",
    "updated_at": "2026-02-17T15:32:10Z",
    "creator_id": 456,
    "buyer": {
      "id": 789,
      "name": "Jane Doe",
      "email": "jane.doe@example.com"
    },
    "item": {
      "id": 321,
      "title": "Premium Coaching Call",
      "type": "onetime"
    },
    "event_type": "dispute.created"
  },
  "created_at": "2026-02-25T10:04:33Z"
}
dispute.updated Fires when a dispute status changes

A dispute has had a status change. The most common transition is needs_responseunder_review after you submit evidence, then eventually won or lost. The payload shape is identical to dispute.created — check data.status to see what changed. If won, the disputed funds and chargeback fee will be returned to you.

Example Payload (dispute resolved as won)
{
  "id": "fe3505d5-1b32-4c04-95bf-5d5f60957b7f",
  "type": "dispute.updated",
  "data": {
    "id": 123,
    "dispute_id": "dp_1Q2w3E4r5T",
    "amount": 75.50,
    "dispute_fee": 15.00,
    "total_amount": 90.50,
    "status": "won",
    "reason": "fraudulent",
    "payment_intent_id": "pi_3Nc8QJ2eZvKYlo2C0xYzAbCd",
    "due_by": "2025-02-08T23:59:59Z",
    "created_at": "2025-02-01T12:00:00Z",
    "updated_at": "2025-02-12T15:30:00Z",
    "creator_id": 456,
    "buyer": {
      "id": 789,
      "name": "John Doe",
      "email": "buyer@example.com"
    },
    "item": {
      "id": 321,
      "title": "Pro Membership",
      "type": "onetime"
    },
    "event_type": "dispute.updated"
  },
  "created_at": "2025-02-12T15:30:00Z"
}

Dispute Status Values

StatusMeaning
needs_responseDispute just opened — submit evidence before due_by.
under_reviewYou submitted evidence and the bank is reviewing it.
wonResolved in your favor — funds and fee returned to you.
lostResolved in the customer's favor — amount and fee deducted.
warning_needs_responseEarly warning — respond quickly to prevent a full chargeback.

Schema

JSON Schema
{
  "type": "object",
  "required": ["id", "type", "data", "created_at"],
  "properties": {
    "id": { "type": "string", "description": "Unique event identifier (UUID)" },
    "type": { "type": "string", "description": "Always 'dispute.updated'" },
    "data": {
      "type": "object",
      "required": ["id", "dispute_id", "amount", "status", "reason", "payment_intent_id", "due_by"],
      "properties": {
        "id": { "type": "integer" },
        "dispute_id": { "type": "string" },
        "amount": { "type": "number" },
        "dispute_fee": { "type": "number" },
        "total_amount": { "type": "number" },
        "status": { "type": "string", "description": "Updated status (e.g., won, lost)" },
        "reason": { "type": "string" },
        "payment_intent_id": { "type": "string" },
        "due_by": { "type": "string", "format": "date-time" },
        "created_at": { "type": "string", "format": "date-time" },
        "updated_at": { "type": "string", "format": "date-time" },
        "creator_id": { "type": "integer" },
        "buyer": { "type": "object", "properties": { "id": { "type": "integer" }, "name": { "type": "string" }, "email": { "type": "string" } } },
        "item": { "type": "object", "properties": { "id": { "type": "integer" }, "title": { "type": "string" }, "type": { "type": "string" } } },
        "event_type": { "type": "string" }
      }
    },
    "created_at": { "type": "string", "format": "date-time" }
  }
}

Example Payload

Example JSON
{
  "id": "fe3505d5-1b32-4c04-95bf-5d5f60957b7f",
  "type": "dispute.updated",
  "data": {
    "id": 123,
    "dispute_id": "dp_1Q2w3E4r5T",
    "amount": 75.50,
    "dispute_fee": 15.00,
    "total_amount": 90.50,
    "status": "won",
    "reason": "fraudulent",
    "payment_intent_id": "pi_3Nc8QJ2eZvKYlo2C0xYzAbCd",
    "due_by": "2026-03-01T23:59:59Z",
    "created_at": "2026-02-17T15:32:10Z",
    "updated_at": "2026-02-25T10:04:33Z",
    "creator_id": 456,
    "buyer": {
      "id": 789,
      "name": "Jane Doe",
      "email": "jane.doe@example.com"
    },
    "item": {
      "id": 321,
      "title": "Premium Coaching Call",
      "type": "onetime"
    },
    "event_type": "dispute.updated"
  },
  "created_at": "2026-02-25T10:04:33Z"
}

Refund Events refund.created

Refund events also use the envelope format. They include a nested stripe object with processor-level identifiers for accounting reconciliation.

refund.created Fires when a refund is successfully issued

A refund has been issued to a customer — whether triggered by you via API, through the dashboard, or automatically. The nested stripe object contains processor-level IDs for cross-referencing with your payment records and for accurate accounting.

ℹ Non-refundable processing fee

data.stripe.refund_cost_stripe_fee is the payment processing fee that is forfeited when a refund is issued. Track this for accurate P&L accounting — your payout will be reduced by both the refunded amount and any non-refundable fees.

Example Payload (Envelope Format)
{
  "id": "fe3505d5-1b32-4c04-95bf-5d5f60957b7f",
  "type": "refund.created",
  "data": {
    "arn": null,
    "refund_id": 149,
    "refund_transaction_id": 23838,
    "refund_cost": 92.69,
    "refund_cost_creator_amount": 90.00,
    "refund_cost_affiliate_commission": null,
    "amount": 90.00,
    "status": "success",
    "created_at": "2025-02-05T09:00:00-06:00",
    "updated_at": "2025-02-05T09:00:00-06:00",
    "refund_type": "partial",
    "reason": "requested_by_customer",
    "buyer": {
      "id": 28866,
      "name": "John Doe",
      "email": "buyer@example.com"
    },
    "item": {
      "id": 2205,
      "title": "Pro Membership",
      "type": "onetime"
    },
    "stripe": {
      "stripe_refund_id": "re_3Px7LkLkHcJBAbcd",
      "stripe_charge_id": "pi_3Px7LkLkHcJBAbcd",
      "refund_cost_stripe_fee": 2.69
    },
    "event_type": "refund.created"
  },
  "created_at": "2025-02-05T09:00:00.000Z"
}

Field Reference

FieldTypeNotes
idstring (UUID)Unique event envelope ID — use for idempotency.
typestringAlways refund.created.
created_atstring (ISO 8601)When the event was generated.
data.refund_idintegerInternal FanBasis refund ID.
data.refund_transaction_idintegerThe original transaction ID that was refunded.
data.refund_costnumberTotal cost of the refund including processing fees.
data.refund_cost_creator_amountnumberAmount deducted from the creator's wallet.
data.amountnumberAmount returned to the buyer.
data.statusstringsuccess when the refund processed successfully.
data.refund_typestringpartial or full.
data.reasonstringWhy issued: requested_by_customer, duplicate, fraudulent.
data.arnstring|nullAcquirer Reference Number, if available from the processor.
data.stripe.stripe_refund_idstringProcessor-level refund ID (Stripe re_ prefix) for reconciliation.
data.stripe.stripe_charge_idstringProcessor-level charge/payment intent ID for the original transaction.
data.stripe.refund_cost_stripe_feenumberProcessing fee forfeited, in dollars. Use for accurate P&L accounting.
data.buyerobjectRefunded customer ID, name, and email.
data.itemobjectThe product that was refunded (id, title, type).

Schema

JSON Schema
{
  "type": "object",
  "required": ["id", "type", "data", "created_at"],
  "properties": {
    "id": { "type": "string", "description": "Unique event identifier (UUID)" },
    "type": { "type": "string", "description": "Always 'refund.created'" },
    "data": {
      "type": "object",
      "required": ["refund_id", "amount", "status", "reason", "buyer", "item"],
      "properties": {
        "arn": { "type": ["string", "null"] },
        "refund_id": { "type": "integer" },
        "refund_transaction_id": { "type": "integer" },
        "refund_cost": { "type": "number", "description": "Total refund cost including fees" },
        "refund_cost_creator_amount": { "type": "number", "description": "Amount deducted from creator" },
        "refund_cost_affiliate_commission": { "type": ["number", "null"] },
        "amount": { "type": "number", "description": "The refunded amount" },
        "status": { "type": "string", "description": "e.g., success, pending" },
        "created_at": { "type": "string", "format": "date-time" },
        "updated_at": { "type": "string", "format": "date-time" },
        "refund_type": { "type": "string", "description": "full or partial" },
        "reason": { "type": "string", "description": "e.g., requested_by_customer" },
        "buyer": { "type": "object", "properties": { "id": { "type": "integer" }, "name": { "type": "string" }, "email": { "type": "string" } } },
        "item": { "type": "object", "properties": { "id": { "type": "integer" }, "title": { "type": "string" }, "type": { "type": "string" } } },
        "stripe": {
          "type": "object",
          "properties": {
            "stripe_refund_id": { "type": "string" },
            "stripe_charge_id": { "type": "string" },
            "refund_cost_stripe_fee": { "type": "number" }
          }
        },
        "event_type": { "type": "string" }
      }
    },
    "created_at": { "type": "string", "format": "date-time" }
  }
}

Example Payload

Example JSON
{
  "id": "fe3505d5-1b32-4c04-95bf-5d5f60957b7f",
  "type": "refund.created",
  "data": {
    "arn": null,
    "refund_id": 149,
    "refund_transaction_id": 23838,
    "refund_cost": 92.69,
    "refund_cost_creator_amount": 90,
    "refund_cost_affiliate_commission": null,
    "amount": 90,
    "status": "success",
    "created_at": "2026-02-17T15:41:55-06:00",
    "updated_at": "2026-02-17T15:41:55-06:00",
    "refund_type": "partial",
    "reason": "requested_by_customer",
    "buyer": {
      "id": 28866,
      "name": "John Smith",
      "email": "john@example.com"
    },
    "item": {
      "id": 2205,
      "title": "Premium Course Bundle",
      "type": "onetime"
    },
    "stripe": {
      "stripe_refund_id": "re_3Sn3p5I79SORlUQS1Exkfaov",
      "stripe_charge_id": "pi_3Sn3p5I79SORlUQS1okl6q3D",
      "refund_cost_stripe_fee": 2.69
    },
    "event_type": "refund.created"
  },
  "created_at": "2026-02-17T15:41:58-06:00"
}

Checkout Sessions

A checkout session is how you create a payment page. Think of it as a product listing that FanBasis hosts for you — you define the name, price, and type, and FanBasis gives you a link customers can use to pay. One checkout session can be shared with unlimited customers and generates a new transaction each time someone pays.

How the checkout flow works

1
You create a checkout session

Call POST /checkout-sessions with your product details. You receive a checkout_session_id and a payment_link.

2
Your customer visits the payment link

Share the link however you like — email, social, your website. FanBasis shows them a secure checkout page.

3
The customer enters their card and pays

FanBasis handles everything: card validation, processing, receipts. You don't touch payment data.

4
You get a webhook notification

As soon as payment succeeds, FanBasis fires a payment.succeeded event to your webhook URL (if configured). A transaction record is also created.

Create a Checkout Session

POST /public-api/checkout-sessions

This is the most important endpoint — you'll call it every time you want to offer a product for purchase. The payment link you get back is ready to use immediately.

✦ When would I use this?

You're launching a new coaching package priced at $199 one-time. You call this endpoint, get a payment link, and paste it into your newsletter. Anyone who clicks and pays gets a transaction recorded automatically.

Request Body
{
  "product": {
    "title": "Premium Membership",
    "description": "Access to all premium features"
  },
  "amount_cents": 2999,
  "application_fee": 0,
  "type": "subscription",
  "metadata": {
    "internal_ref": "plan_premium_monthly"
  },
  "expiration_date": "2025-12-31",
  "subscription": {
    "frequency_days": 30,
    "auto_expire_after_x_periods": null,
    "free_trial_days": 7,
    "initial_fee": 0,
    "initial_fee_days": 0
  },
  "success_url": "https://yoursite.com/success",
  "webhook_url": "https://yoursite.com/webhooks/fanbasis"
}
Request
curl -X POST https://www.fanbasis.com/public-api/checkout-sessions \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "product": {
      "title": "Pro Monthly Membership",
      "description": "Full access including private Discord + weekly calls"
    },
    "amount_cents": 2999,
    "type": "subscription",
    "subscription": {
      "frequency_days": 30,
      "free_trial_days": 7
    },
    "success_url": "https://yoursite.com/welcome",
    "metadata": { "plan": "pro" }
  }'
Response
{
  "status": "success",
  "message": "Created Product",
  "data": {
    "id": "NLxj6",
    "checkout_session_id": 345424,
    "payment_link": "https://www.fanbasis.com/agency-checkout/your-handle/NLxj6"
  },
  "request_id": "Root=1-xxxxxxxx-xxxxxxxxxxxxxxxxxxxx"
}
✓ What to do with the payment_link

Store the checkout_session_id (numeric) and id (short alphanumeric) in your own database so you can look up transactions and subscriptions later. The payment_link is what you share — embed it as a button, paste it in emails, or add it to your link-in-bio. The URL format includes your creator handle.

Look Up a Checkout Session

GET /public-api/checkout-sessions/:checkoutSessionId

Retrieves all the details of a checkout session — the product info, pricing, subscription settings, and expiration date. Useful if you've lost track of a session's configuration.

Path Parameters

ParameterTypeRequiredDescription
checkoutSessionIdstringYesThe checkout_session_id returned when you created the session.
Request
curl "https://www.fanbasis.com/public-api/checkout-sessions/NLxj6" \
  -H "x-api-key: YOUR_API_KEY"
Response
{
  "status": "success",
  "data": {
    "product": {
      "id": "NLxj6",
      "title": "Pro Monthly Membership",
      "description": "Full access including private Discord + weekly calls"
    },
    "amount_cents": 2999,
    "type": "subscription",
    "subscription": { "frequency_days": 30, "free_trial_days": 7 },
    "success_url": "https://yoursite.com/welcome"
  }
}

Delete a Checkout Session

DELETE /public-api/checkout-sessions/:checkoutSessionId

Permanently deletes a checkout session and deactivates its payment link. Anyone who visits the link afterwards will see a "not found" error.

⚠ This cannot be undone

Existing transactions and subscriptions created through this session are not affected — only the ability to make new purchases is removed.

Path Parameters

ParameterTypeRequiredDescription
checkoutSessionIdstringYesThe ID of the checkout session to delete.
Request
curl -X DELETE "https://www.fanbasis.com/public-api/checkout-sessions/NLxj6" \
  -H "x-api-key: YOUR_API_KEY"
Response
{ "status": "success", "message": "Checkout session deleted successfully", "data": [] }

Get Transactions for a Checkout Session

GET /public-api/checkout-sessions/:checkoutSessionId/transactions

Returns all transactions associated with a specific checkout session. Useful when a single session has produced multiple payments (e.g. subscription renewals tied to the same session).

Path Parameters

ParameterTypeRequiredDescription
checkoutSessionIdstringYesThe checkout session ID.

Query Parameters

ParameterTypeRequiredDescription
pageintegerNoWhich page of results to show. Starts at 1.
per_pageintegerNoHow many results per page (max 100).
Request
curl "https://www.fanbasis.com/public-api/checkout-sessions/NLxj6/transactions?page=1&per_page=20" \
  -H "x-api-key: YOUR_API_KEY"
Response
{
  "status": "success",
  "data": {
    "transactions": [
      {
        "id": "txn_abc123",
        "fan": { "name": "Jane Doe", "email": "jane@example.com" },
        "service": { "title": "Pro Monthly Membership", "price": 29.99 },
        "fee_amount": 1.20,
        "net_amount": 28.79
      }
    ],
    "pagination": { "current_page": 1, "total_pages": 3, "total_items": 48, "has_more": true }
  }
}

Create an Embedded Checkout Session

POST /public-api/checkout-sessions/embedded

Creates a checkout session designed to be embedded directly inside your app or website, rather than redirecting to a separate page. Requires an existing FanBasis product_id. Returns a checkout_session_secret you use to construct the embedded checkout URL.

✓ Reusable session secret

The checkout_session_secret is scoped to your creator account, not to a single product. You can reuse the same secret and swap the product_id in the URL — you do not need to create a new embedded session for every product.

Request Body
{
  "product_id": "NLxj6",
  "metadata": {
    "user_id": "usr_abc123",
    "source": "in-app"
  }
}
Request
curl -X POST https://www.fanbasis.com/public-api/checkout-sessions/embedded \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "product_id": "NLxj6", "metadata": { "source": "in-app" } }'
Response
{
  "status": "success",
  "data": {
    "checkout_session_secret": "550e8400-e29b-41d4-a716-446655440000",
    "created_at": "2025-01-15T10:00:00Z"
  }
}
ℹ Embedded checkout URL format

Construct the embeddable URL using your creator handle, the product id, and the checkout_session_secret:

https://embedded.fanbasis.io/session/{your-handle}/{product-id}/{checkout_session_secret}

Load this URL inside an <iframe> or open it as a popup to display the embedded payment form.

Customers

Your customer list includes everyone who has ever purchased from you through FanBasis. The Customers API lets you search your list, view saved payment methods, and charge customers again directly — without them needing to go through checkout.

✦ When is this useful?

A customer wants to add another product or pay for a one-time service. You find them in your customer list, grab their saved card, and charge them directly — all without sending them a new payment link.

List Your Customers

GET /public-api/customers

Returns a searchable, paginated list of all your customers — with their total spend, transaction count, and last payment date.

Query Parameters

ParameterTypeRequiredDescription
searchstringNoType a name, email, or phone number to search for a specific customer.
pageintegerNoPage number (starts at 1).
per_pageintegerNoResults per page (max 100).
Request
curl "https://www.fanbasis.com/public-api/customers?search=jane@example.com" \
  -H "x-api-key: YOUR_API_KEY"
Response
{
  "status": "success",
  "data": {
    "customers": [
      {
        "id": "cust_1",
        "name": "Jane Doe",
        "email": "jane@example.com",
        "total_transactions": 5,
        "total_spent": 149.95,
        "last_transaction_date": "2025-01-10T00:00:00Z"
      }
    ],
    "pagination": { "current_page": 1, "total_items": 58 }
  }
}

Get a Customer's Saved Payment Methods

GET /public-api/customers/:customerId/payment-methods

Shows all payment cards a customer has on file. You'll need the payment method ID to charge them directly.

Path Parameters

ParameterTypeRequiredDescription
customerIdstringYesThe customer's ID (from the customer list).
Request
curl "https://www.fanbasis.com/public-api/customers/cust_1/payment-methods" \
  -H "x-api-key: YOUR_API_KEY"
Response
{
  "status": "success",
  "data": {
    "customer": { "name": "Jane Doe", "email": "jane@example.com" },
    "payment_methods": [
      {
        "id": "pm_abc",
        "type": "card",
        "last4": "4242",
        "brand": "visa",
        "exp_month": 12,
        "exp_year": 2027,
        "is_default": true
      }
    ]
  }
}

Charge a Customer Directly

POST /public-api/customers/:customerId/charge

Charges a customer using a saved payment method. No checkout page needed — the charge happens immediately.

Path Parameters

ParameterTypeRequiredDescription
customerIdstringYesThe customer's ID.
Request Body
{
  "payment_method_id": "pm_abc123xyz",
  "service_id": "svc_premium_monthly",
  "amount_cents": 1999,
  "description": "Monthly premium subscription charge",
  "metadata": {}
}
Request
curl -X POST "https://www.fanbasis.com/public-api/customers/cust_1/charge" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "payment_method_id": "pm_abc",
    "service_id": "svc_pro_plan",
    "amount_cents": 1999,
    "description": "Upgrade to Pro plan",
    "metadata": {}
  }'
Response
{
  "status": "success",
  "data": {
    "charge_id": "ch_abc123",
    "amount": 19.99,
    "status": "succeeded",
    "created_at": "2025-01-15T14:30:00Z"
  }
}

Subscribers

The Subscribers endpoint gives you a unified view of who is subscribed to what — combining customer profile info with their subscription status across all your products. Think of it as a live member directory.

List All Subscribers

GET /public-api/subscribers

Returns every subscriber across all your products. Filter by customer or product to narrow results.

Query Parameters

ParameterTypeRequiredDescription
product_idstringNoShow only subscribers to this product.
customer_idstringNoShow only subscriptions belonging to this customer.
statusstringNoFilter by subscription status (e.g. active, cancelled, expired).
pageintegerNoPage number.
per_pageintegerNoResults per page (max 100).
Request
curl "https://www.fanbasis.com/public-api/subscribers?product_id=NLxj6" \
  -H "x-api-key: YOUR_API_KEY"
Response
{
  "status": "success",
  "data": {
    "subscribers": [
      {
        "id": "sub_1",
        "customer": { "name": "Jane Doe", "email": "jane@example.com" },
        "product": { "title": "Pro Monthly Membership", "price": 29.99 },
        "subscription": {
          "status": "active",
          "payment_frequency": 30,
          "created_at": "2025-01-01T00:00:00Z"
        }
      }
    ],
    "pagination": { "current_page": 1, "total_items": 80 }
  }
}

Get Subscriptions for a Checkout Session

GET /public-api/checkout-sessions/:checkoutSessionId/subscriptions

Returns all subscriptions created from a specific checkout session. Useful for subscription products that have multiple subscribers through the same session.

Path Parameters

ParameterTypeRequiredDescription
checkoutSessionIdstringYesThe checkout session ID.

Query Parameters

ParameterTypeRequiredDescription
pageintegerNoPage number.
per_pageintegerNoResults per page (max 100).
Request
curl "https://www.fanbasis.com/public-api/checkout-sessions/NLxj6/subscriptions?page=1" \
  -H "x-api-key: YOUR_API_KEY"
Response
{
  "status": "success",
  "data": {
    "subscriptions": [
      {
        "id": "sub_1",
        "email": "jane@example.com",
        "subscription_status": "active",
        "next_renewal_date": "2025-02-15T00:00:00Z"
      }
    ],
    "pagination": { "current_page": 1, "total_pages": 1, "total_items": 12 }
  }
}

Get Subscriptions for a Product

GET /public-api/checkout-sessions/:productId/subscriptions

Lists every subscriber to a specific product. Shows their email, subscription status, and when they'll next be billed. Great for managing your member list.

Path Parameters

ParameterTypeRequiredDescription
productIdstringYesThe checkout_session_id of the product to get subscribers for.

Query Parameters

ParameterTypeRequiredDescription
pageintegerNoPage number.
per_pageintegerNoResults per page (max 100).
Request
curl "https://www.fanbasis.com/public-api/checkout-sessions/NLxj6/subscriptions?page=1" \
  -H "x-api-key: YOUR_API_KEY"
Response
{
  "status": "success",
  "data": {
    "subscriptions": [
      {
        "id": "sub_1",
        "email": "jane@example.com",
        "subscription_status": "active",
        "next_renewal_date": "2025-02-15T00:00:00Z"
      }
    ],
    "pagination": { "current_page": 1, "total_pages": 2, "total_items": 40 }
  }
}

Cancel a Subscription

DELETE /public-api/checkout-sessions/:checkoutSessionId/subscriptions/:subscriptionId

Cancels a customer's subscription. They keep access until the end of the current billing period but won't be charged again.

● Finding the subscriptionId

The subscriptionId is the id field returned in the subscription list above — not the customer's user ID.

Path Parameters

ParameterTypeRequiredDescription
checkoutSessionIdstringYesThe ID of the product the subscription belongs to.
subscriptionIdstringYesThe subscription ID from the subscription list (the id field).
Request
curl -X DELETE \
  "https://www.fanbasis.com/public-api/checkout-sessions/NLxj6/subscriptions/sub_xyz789" \
  -H "x-api-key: YOUR_API_KEY"
Response
{
  "status": "success",
  "message": "Subscription cancelled successfully",
  "data": { "id": "sub_1", "cancelled_at": "2025-01-15T14:00:00Z", "subscription_status": "cancelled" }
}

Refund a Transaction

POST /public-api/checkout-sessions/transactions/:transactionId/refund

Issues a full or partial refund for a payment. For a full refund, don't include amount_cents. For a partial refund, specify exactly how much to refund.

⚠ Refunds are irreversible

Once issued, a refund cannot be canceled. The customer receives the refunded amount back to their original payment method within a few business days.

Path Parameters

ParameterTypeRequiredDescription
transactionIdstringYesThe ID of the transaction to refund.
Request Body
{
  "amount_cents": 1500
}
Request
# Full refund — omit amount_cents
curl -X POST "https://www.fanbasis.com/public-api/checkout-sessions/transactions/txn_abc123/refund" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
Response
{
  "status": "success",
  "message": "Transaction refunded successfully",
  "data": { "refund_id": "re_1234567890", "transaction_id": "txn_abc123", "refund_amount": 29.99, "refund_type": "full" }
}

Extend a Subscription

POST /public-api/checkout-sessions/:checkoutSessionId/extend-subscription

Pushes out a customer's next billing date by a given number of days. Use this to comp members for downtime, run a loyalty promotion, or manually extend access.

Path Parameters

ParameterTypeRequiredDescription
checkoutSessionIdstringYesThe ID of the product the subscription belongs to.
Request Body
{
  "user_id": "usr_abc123",
  "duration_days": 30
}
Request
curl -X POST "https://www.fanbasis.com/public-api/checkout-sessions/NLxj6/extend-subscription" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "user_id": "usr_456", "duration_days": 30 }'
Response
{
  "status": "success",
  "message": "Subscription extended successfully",
  "data": { "subscription_id": "sub_1", "new_completion_date": "2025-04-15T00:00:00Z" }
}

Discount Codes

Discount codes let you offer reduced pricing to specific customers or as part of a promotion. You control the discount type (percentage or fixed amount), how long it applies, when it expires, and how many times it can be used.

✦ Ideas for using discount codes

"SUMMER20" — 20% off the first payment. Share on social media to attract new subscribers.
"LOYALVIP" — $10 off forever for your longest-standing members.
"WELCOME50" — 50% off the first payment for new sign-ups from a referral campaign.

List Discount Codes

GET /public-api/discount-codes

Returns all your discount codes. Use the search parameter to quickly find a specific code.

Query Parameters

ParameterTypeRequiredDescription
searchstringNoFilter codes by their code string or description.
per_pageintegerNoResults per page (max 100).
Request
curl "https://www.fanbasis.com/public-api/discount-codes" \
  -H "x-api-key: YOUR_API_KEY"
Response
{
  "status": "success",
  "data": {
    "current_page": 1,
    "data": [
      {
        "id": 1,
        "code": "SUMMER20",
        "discount_type": "percentage",
        "value": 20,
        "duration": "once",
        "is_active": true
      }
    ],
    "total": 1
  }
}

Create a Discount Code

POST /public-api/discount-codes

Creates a new discount code with the settings you define.

Request Body
{
  "code": "SUMMER20",
  "description": "20% off for new subscribers",
  "discount_type": "percentage",
  "value": 20,
  "duration": "multiple_months",
  "no_of_months": 3,
  "expiry": "2025-08-31",
  "limited_redemptions": true,
  "usable_number": 100,
  "one_time": true,
  "service_ids": [101, 102]
}
Request
curl -X POST https://www.fanbasis.com/public-api/discount-codes \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "code": "SUMMER20",
    "discount_type": "percentage",
    "value": 20,
    "duration": "once",
    "service_ids": [101],
    "expiry": "2025-08-31",
    "one_time": true
  }'
Response
{ "status": "success", "message": "Discount code created successfully", "data": {} }

Get a Discount Code

GET /public-api/discount-codes/:id

Fetches the details of one discount code, including how many times it's been used.

Path Parameters

ParameterTypeRequiredDescription
idstringYesThe discount code's ID.
Request
curl "https://www.fanbasis.com/public-api/discount-codes/1" \
  -H "x-api-key: YOUR_API_KEY"
Response
{
  "status": "success",
  "data": {
    "id": 1,
    "code": "SUMMER20",
    "discount_type": "percentage",
    "value": 20,
    "duration": "once",
    "expiry": "2025-08-31",
    "times_used": 12,
    "is_active": true
  }
}

Update a Discount Code

PUT /public-api/discount-codes/:id

Updates an existing discount code. Only include the fields you want to change.

Path Parameters

ParameterTypeRequiredDescription
idstringYesThe discount code's ID.
Request Body
{
  "code": "SUMMER20",
  "description": "Updated discount — 20% off for 3 months",
  "discount_type": "percentage",
  "value": 20,
  "duration": "multiple_months",
  "no_of_months": 3,
  "expiry": "2026-03-31",
  "limited_redemptions": true,
  "usable_number": 200,
  "one_time": false,
  "service_ids": [101, 102]
}
Request
curl -X PUT "https://www.fanbasis.com/public-api/discount-codes/1" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "expiry": "2025-12-31" }'
Response
{ "status": "success", "message": "Discount code updated successfully", "data": {} }

Delete a Discount Code

DELETE /public-api/discount-codes/:id

Deletes a discount code. Customers with active subscriptions already using this code won't be affected — they'll keep their discount.

Path Parameters

ParameterTypeRequiredDescription
idstringYesThe discount code's ID.
Request
curl -X DELETE "https://www.fanbasis.com/public-api/discount-codes/1" \
  -H "x-api-key: YOUR_API_KEY"
Response
{ "status": "success", "message": "Discount code deleted successfully", "data": [] }

Products

Products (called "services" in some parts of the API) are the items you've set up to sell. Each product has its own payment link. Use the Products endpoint to pull a list of everything you offer — useful for building dynamic product pages or dropdowns in your own app.

List Your Products

GET /public-api/products

Returns all your products with their titles, prices, and ready-to-use payment links.

Query Parameters

ParameterTypeRequiredDescription
pageintegerNoPage number.
per_pageintegerNoResults per page (max 100).
Request
curl "https://www.fanbasis.com/public-api/products" \
  -H "x-api-key: YOUR_API_KEY"
Response
{
  "status": "success",
  "data": {
    "current_page": 1,
    "data": [
      {
        "id": "prod_1",
        "title": "Pro Monthly",
        "description": "Full access to all features, billed monthly.",
        "price": 29.99,
        "payment_link": "https://www.fanbasis.com/agency-checkout/your-handle/prod_1"
      }
    ],
    "total": 5
  }
}

Transactions

A transaction is a record of a single completed payment. Every time a customer pays — one-time or recurring — a transaction is created. The Transactions API lets you pull detailed records including customer info, the product sold, FanBasis's fee, and your net payout.

Look Up a Transaction

GET /public-api/transactions/:transactionId

Returns the full details of a single payment. Useful for customer support lookups, accounting, or building transaction receipts.

Path Parameters

ParameterTypeRequiredDescription
transactionIdstringYesThe transaction ID. This appears in webhook events and in the transactions list as the id field.
Request
curl "https://www.fanbasis.com/public-api/transactions/txn_abc123" \
  -H "x-api-key: YOUR_API_KEY"
Response
{
  "status": "success",
  "data": {
    "id": "txn_abc123",
    "transaction_date": "2025-01-15T14:30:00Z",
    "fan": {
      "name": "Jane Doe",
      "email": "jane@example.com",
      "country_code": "US"
    },
    "service": {
      "title": "Pro Monthly Membership",
      "price": 29.99
    },
    "fee_amount": 1.20,
    "net_amount": 28.79,
    "refunds": []
  }
}

Get All Transactions

GET /public-api/checkout-sessions/transactions

Returns every payment that's been made across all your products. Each result shows who paid, what they bought, your fee, and your net payout. You can filter by product or customer.

Query Parameters

ParameterTypeRequiredDescription
product_idstringNoOnly show transactions for this product.
customer_idstringNoOnly show transactions from this customer.
pageintegerNoWhich page of results to show. Starts at 1.
per_pageintegerNoHow many results per page (max 100).
Request
curl "https://www.fanbasis.com/public-api/checkout-sessions/transactions?page=1&per_page=20" \
  -H "x-api-key: YOUR_API_KEY"
Response
{
  "status": "success",
  "data": {
    "transactions": [
      {
        "id": "txn_abc123",
        "fan": { "name": "Jane Doe", "email": "jane@example.com" },
        "service": { "title": "Pro Monthly Membership", "price": 29.99 },
        "fee_amount": 1.20,
        "net_amount": 28.79
      }
    ],
    "pagination": { "current_page": 1, "total_pages": 5, "total_items": 95, "has_more": true }
  }
}

Refunds

The FanBasis API allows you to issue full or partial refunds for successful payments. Refunds are processed back to the original payment method. Because FanBasis acts as your Merchant of Record, refund eligibility and timing may be governed by your account's agreement.

Refund Rules

Before initiating a refund, all of the following conditions must be satisfied:

RuleDetails
Payment must have succeeded Only payments with status succeeded are eligible. Failed, pending, or cancelled payments cannot be refunded.
Within processor refund window Refunds must be initiated within the payment processor's refund window. For Affirm and Afterpay transactions this is 120 days; for Cash App Pay it is 90 days. Requests outside the processor window are automatically blocked. Contact support@fanbasis.com for off-platform options if the window has passed.
No duplicate in-flight refund If a refund request is already in a pending state for the same payment, a second request will be rejected until the first completes.
Amount ≤ amount paid The cumulative refund amount across all partial refunds cannot exceed the original payment amount. Overage requests are rejected with REFUND_AMOUNT_EXCEEDS_PAID_AMOUNT.
Not already fully refunded If a payment has already been fully refunded, additional refund requests are rejected with PAYMENT_ALREADY_REFUNDED.

Create a Refund

Issue a full or partial refund for a completed payment:

POST /public-api/checkout-sessions/transactions/{transactionId}/refund

Replace {transactionId} with the transaction ID (returned in the checkout session response or webhook payload).

Request Body

FieldTypeRequiredDescription
amountintegerNoAmount to refund in the smallest currency unit (e.g., cents). Omit for a full refund of the remaining amount.
reasonstringNoHuman-readable reason for the refund. Stored on the refund record and may appear in customer communications.
Request — Partial Refund
POST /checkout-sessions/pay_abc123/refund
x-api-key: YOUR_API_KEY
Content-Type: application/json

{
  "amount": 2500,
  "reason": "Customer requested partial refund for unused portion"
}
Response — 200 OK
{
  "status": "success",
  "data": {
    "refund_id": "ref_XjK82mPqW",
    "payment_id": "pay_abc123",
    "amount": 2500,
    "currency": "usd",
    "status": "pending",
    "reason": "Customer requested partial refund for unused portion",
    "created_at": "2025-03-15T10:42:00Z"
  }
}

Refund Status Reference

Track the progress of a refund using the following status values:

StatusMeaningAction
succeeded The refund has been successfully processed and funds have been returned. No action needed. A receipt was automatically sent to the customer.
pending The refund is in progress. Typically resolves within 5–10 business days. Wait for the refund.created webhook event — it fires with data.status: "succeeded" when complete.
failed The refund could not be processed. Possible causes: payment was disputed, insufficient wallet balance, or bank issue. Contact support@fanbasis.com with the refund ID for assistance.
ℹ️ Webhook Notifications

Subscribe to the refund.created webhook event to receive real-time updates when a refund is processed. Check data.status in the payload — a value of succeeded means the refund completed successfully. See the refund.created event reference for the full payload schema.

Disputes

A dispute (also called a chargeback) occurs when a customer contacts their bank to reverse a charge rather than requesting a refund from you directly. FanBasis will notify you when a dispute is filed, but it is your responsibility to provide compelling evidence through the Resolution Center. FanBasis then files your response with the card networks.

⚠️ Time-sensitive

Dispute response deadlines are strict and set by the card network. Respond as quickly as possible — if you miss the deadline, the dispute will automatically resolve in the customer's favor and the funds will be reversed.

Dispute Lifecycle

Each dispute progresses through the following statuses:

StatusMeaning
opened A dispute has been filed by the customer's bank. FanBasis sends a dispute.created webhook. Respond as quickly as possible.
challenged You have submitted evidence to counter the dispute. The card network is reviewing your submission.
won The dispute was resolved in your favor. No funds were reversed.
lost The dispute was resolved in the customer's favor. The disputed amount (plus any chargeback fee) has been debited from your balance.
accepted You accepted the dispute without contesting it. Funds were returned to the customer.
expired The response window passed without a submission. Treated the same as lost.
cancelled The customer withdrew the dispute before it was resolved.

Responding to a Dispute

To contest a dispute, gather the relevant evidence and submit it through your FanBasis dashboard as quickly as possible. The type of evidence depends on the reason for the dispute:

🔒 Fraudulent Transaction
IP address and device at time of purchase, login timestamps, account activity logs, browser fingerprint, and any prior successful transactions from the same account.
📦 Product Not Received
Screenshots or logs showing successful login/access, timestamped activity (downloads, sessions, content viewed), and confirmation emails sent to the customer.
❌ Not as Described
Product description from your website at the time of purchase, screenshots of what was delivered, and any customer communication acknowledging receipt or usage.
📋 All Cases
Purchase invoice, accepted Terms of Service and Refund Policy, and any support communication showing your attempts to resolve the issue before the chargeback was filed.
✓ Evidence Tips

Submit files in PDF, JPG, or PNG format only. Label each file clearly (e.g., Login_Log_March5.pdf). Include timestamps with timezone. Do not submit links or URLs — only attached files are accepted by card networks.

Dispute Webhook Events

FanBasis emits two webhook events for the dispute lifecycle. Subscribe to both to stay informed in real time:

EventWhen it fires
dispute.createdA dispute was filed by the customer's bank. Act immediately and submit evidence as soon as possible.
dispute.updatedThe dispute's status changed (e.g., challenged, won, lost, accepted, expired, cancelled). Check data.status to see the new state.
ℹ How to track each stage

Both events share the same payload shape. Use the data.status field to determine the current stage of the dispute. See the Dispute Events reference for the full payload schema and field descriptions.

Webhook Payload — dispute.created
{
  "webhook_id": "wh_abc123",
  "event_type": "dispute.created",
  "timestamp": "2025-04-10T14:22:00Z",
  "data": {
    "dispute_id": "dis_XjK82mPqW",
    "payment_id": "pay_abc123",
    "amount": 4900,
    "currency": "usd",
    "reason": "fraudulent",
    "status": "opened",
    "created_at": "2025-04-10T14:22:00Z",
    "respond_by": "2025-04-14T14:22:00Z"
  }
}

Rate Limits & Pagination

Rate Limiting

The FanBasis API enforces rate limits to protect platform stability. When you exceed the limit, the API returns HTTP 429 Too Many Requests with the error code TOO_MANY_REQUESTS.

ℹ Rate limit thresholds

Specific rate limit thresholds vary by account and endpoint. Contact support@fanbasis.com for the limits that apply to your account.

✓ Best Practice

Implement exponential backoff when you receive a 429 response. Check the Retry-After header included in the response for how long to wait before retrying.

Pagination

All list endpoints (customers, transactions, subscribers, discount codes, products) return paginated results. Use the page and per_page parameters to navigate through result sets.

Query Parameters

ParameterTypeDefaultDescription
pageinteger1The page number to retrieve. Starts at 1.
per_pageinteger20Number of records per page. Maximum value is 100.
sortstringcreated_atField to sort by. Supported values vary by endpoint.
orderstringdescasc or desc.

Paginated Response Structure

Response — List Endpoint
{
  "status": "success",
  "data": [ ...array of items... ],
  "meta": {
    "current_page": 1,
    "per_page": 20,
    "total": 142,
    "total_pages": 8,
    "has_next_page": true,
    "has_prev_page": false
  }
}

Iterating All Pages — Example

JavaScript — Fetch All Customers
async function getAllCustomers(apiKey) {
  const allCustomers = [];
  let page = 1;
  let hasMore = true;

  while (hasMore) {
    const res = await fetch(
      `https://www.fanbasis.com/public-api/customers?page=${page}&per_page=100`,
      { headers: { 'x-api-key': apiKey } }
    );
    const json = await res.json();
    allCustomers.push(...json.data);
    hasMore = json.meta.has_next_page;
    page++;
  }

  return allCustomers;
}

Error Reference

When something goes wrong, the API returns a descriptive error response. Here's what each HTTP status code means and what to do about it.

Code Status What happened What to do
200 OK Request succeeded. Nothing — all good! Your data is in the data field.
201 Created A new resource was created. Store the returned ID for future requests.
400 Bad Request Your request was invalid. Check the errors field in the response for field-by-field details.
401 Unauthorized API key missing or wrong. Check that your x-api-key header is included and spelled correctly.
403 Forbidden You don't have permission. Your account may not have access to this feature. Contact support.
404 Not Found The resource doesn't exist. Double-check the ID in your URL. It may have been deleted.
500 Server Error Something broke on our end. Try again in a moment. Contact FanBasis support if it keeps happening.

Error Response Format

Example error response when a required field is missing:

Response
{
  "status": "error",
  "message": "Validation failed",
  "data": [],
  "errors": {
    "code": ["The code field is required."],
    "service_ids": ["At least one service ID is required."]
  }
}
✓ Need help?

If you're stuck, reach out to support@fanbasis.com. Include the full request and response in your message to get a faster resolution.

Specific Error Codes

In addition to HTTP status codes, the API response body includes a machine-readable code field that pinpoints the exact problem. Use this field for branching error-handling logic in your application.

Error Response Shape
{
  "status": "error",
  "message": "Human-readable description",
  "code": "MACHINE_READABLE_CODE",
  "errors": {}
}

Payments & Checkout

Error Code HTTP Description
CHECKOUT_SESSION_CONSUMED 400 The checkout session is no longer available (e.g. it has been deleted or explicitly closed). Active sessions can accept multiple payments.
PAYMENT_NOT_SUCCEEDED 400 The referenced payment has not reached a succeeded status. Check payment status before proceeding.
PREVIOUS_PAYMENT_PENDING 400 Cannot create a new charge — a previous payment for this subscription is still processing.
TOTAL_PAYMENT_AMOUNT_BELOW_MINIMUM_AMOUNT 400 Cart total is below the minimum amount required to process a payment through the gateway.
NO_ELIGIBLE_PAYMENT_METHODS 400 After applying all filters (country, currency, etc.) no valid payment methods remain for this transaction.
UNSUPPORTED_COUNTRY 400 The buyer's country is not yet supported. Check the supported countries list.
UNSUPPORTED_CURRENCY 400 The requested currency is not supported. Currently USD and select currencies are available.

Refunds

Error Code HTTP Description
PAYMENT_ALREADY_REFUNDED 400 This payment has already been fully refunded. Duplicate refund requests are not allowed.
PAYMENT_HAS_BEEN_REFUNDED 400 The payment ID has been fully refunded and no further refunds can be applied.
REFUND_WINDOW_EXPIRED 400 The refund window for this processor has closed (120 days for Affirm/Afterpay, 90 days for Cash App Pay). Contact support@fanbasis.com for off-platform options.
REFUND_AMOUNT_EXCEEDS_PAID_AMOUNT 400 The requested refund amount (including any previous partial refunds) exceeds the original paid amount.
EXISTING_REFUND_REQUEST_PROCESSING 409 A refund with status pending is already being processed for this payment. Wait for it to settle before submitting another.
ZERO_AMOUNT_PAYMENT_REFUND_NOT_ALLOWED 400 Cannot refund a payment with a zero-currency amount.

Discount Codes

Error Code HTTP Description
INVALID_DISCOUNT_CODE 400 The discount code does not exist or cannot be applied to any product in the cart.
DISCOUNT_CODE_EXPIRED 400 The discount code is past its expires_at date and is no longer valid.
DISCOUNT_CODE_USAGE_LIMIT_EXCEEDED 400 The code has reached its maximum usage limit and cannot be applied to new orders.
DISCOUNT_CODE_ALREADY_EXISTS 409 A discount code with this code string already exists. Use a unique code value.
DISCOUNT_NOT_AVAILABLE_FOR_PRODUCT 400 The discount code is restricted to specific products and cannot be applied to one or more items in the cart.

Subscriptions

Error Code HTTP Description
SUBSCRIPTION_INACTIVE 400 The subscription is not in an active state. Check its current status before performing operations.
SUBSCRIPTION_EXPIRED 400 The subscription's billing period has ended. No new charges can be created.
SUBSCRIPTION_PAYMENT_RETRY_LIMIT_EXCEEDED 400 The subscription has hit the maximum number of payment retry attempts. Manual intervention is required.

Authentication & Rate Limiting

Error Code HTTP Description
UNAUTHORIZED 401 No API key provided, or the key is invalid / lacks the required scope for this action.
TOO_MANY_REQUESTS 429 Rate limit exceeded. Check the Retry-After header for how long to wait, then retry with exponential backoff.

General

Error Code HTTP Description
INVALID_REQUEST_BODY 400 The request body is malformed JSON or fails schema validation. Check the errors field for field-level details.
INVALID_REQUEST_PARAMETERS 400 One or more request parameters have invalid semantics (e.g. a date set in the past).
NOT_FOUND 404 The requested resource does not exist. Verify the ID is correct and the resource has not been deleted.
INTERNAL_SERVER_ERROR 500 An unexpected server-side error occurred. Log the request details and contact support if it persists.
✓ Need help?

If you're stuck, reach out to support@fanbasis.com. Include the full request and response in your message to get a faster resolution.

Frequently Asked Questions

Answers to the most common questions about integrating with FanBasis. If you don't find what you need here, reach out to support@fanbasis.com.

Getting Started & Setup
Do I need to wait for account verification before I can start integrating?

No — you can start building right away using your sandbox API key. Sandbox mode works exactly like production: create checkout sessions, fire webhooks, test subscription flows. No real charges are ever made. Once your account is verified, switch to your live API key to go live.

Where do I find my API key?

Log into your FanBasis dashboard and go to the API Keys section. Copy your API key from there. You'll have a separate key for sandbox (test) mode and live (production) mode. Use the appropriate key for each environment.

Can I have multiple API keys or rotate them?

Yes. You can generate a new API key from the API Keys section in your FanBasis dashboard at any time. Once you generate a new key, the old one is invalidated. Make sure to update all your integrations before rotating. We recommend storing your key as an environment variable (e.g., FANBASIS_API_KEY), never in source code.

Can I use FanBasis without a registered business?

Yes. You can onboard as an individual creator or solopreneur without a registered business entity. You'll still need to complete identity verification, but you don't need formal business registration to accept payments through FanBasis.

Payments & Checkout
What's the difference between a checkout session and a product?

A product is a persistent item in your catalog (e.g., "Pro Membership"). A checkout session is a specific instance of selling something — it's the payment page you create and send to a customer. One product can have many checkout sessions. The session is what holds the price, payment type, and the resulting payment_link URL.

Can I create a checkout session without adding a product in the dashboard first?

Yes. You can pass product details (title, description) directly in the checkout session request body via the product object. You don't need a pre-existing product ID. This is useful for dynamic, one-off payment links.

Can I embed the checkout directly on my website instead of redirecting customers?

Yes — use the POST /checkout-sessions/embedded endpoint. Pass a product_id and you'll receive a checkout_session_secret you use to initialize the embedded checkout widget on your frontend. This keeps customers on your page throughout the payment experience.

See the Embedded Checkout section for the full example.

How do I charge a customer again without creating a new checkout session?

Use the Charge a Customer endpoint. If you already have a customer in FanBasis (they've paid before), you can charge their saved payment method directly without requiring them to go through checkout again. This is ideal for usage-based billing, one-time top-ups, or custom billing cycles.

What happens if a checkout session expires before the customer pays?

By default, checkout sessions expire after a set time period. Once expired, the payment link no longer works. FanBasis will fire a payment.expired webhook event. You can create a new session and send the customer a fresh link if needed. You can also delete a session manually before it expires using the Delete Checkout Session endpoint.

Can I give a customer a discount on their checkout?

Yes. First create a discount code using the Discount Codes API (e.g., 20% off, or $10 off), then include the discount_code field in your checkout session request. The discount will automatically be applied to the customer's checkout. You can also share the code directly with customers to enter themselves at checkout.

Subscriptions & Billing
How do I set up a recurring subscription?

When creating a checkout session, set "type": "subscription" and include a subscription object with frequency_days (how often to bill, e.g., 30 for monthly) and optionally a billing_cycles_count if you want the subscription to end after a fixed number of renewals.

Once the customer pays, FanBasis automatically handles recurring billing on the schedule you set. Listen for subscription.renewed webhooks to track each renewal.

Can I extend a subscription's end date?

Yes. Use the Extend Subscription endpoint and pass a user_id and duration_days value. This is useful for offering free trial extensions, compensating customers for downtime, or adding bonus access as a reward.

What happens to a subscriber's data when I cancel their subscription?

Canceling a subscription stops future billing — but the customer's record is preserved in FanBasis. The subscription record, transaction history, and customer profile remain accessible through the API. FanBasis fires a subscription.canceled webhook event, which you can use to revoke access in your own system.

Can customers manage or cancel their own subscriptions?

Subscription management is currently handled through the FanBasis dashboard or via the API. If you want customers to be able to cancel themselves, you can build a self-service portal that calls the Cancel Subscription endpoint on the customer's behalf (using your API key server-side, never in the browser).

Webhooks
How do I know a webhook actually came from FanBasis?

Each webhook request includes a signature in the headers. Compute an HMAC-SHA256 hash of the raw request body using your webhook secret and compare it to the signature header. If they match, the request is genuine. If not, discard it — it wasn't sent by FanBasis.

Never skip signature verification in production, even if the request looks legitimate.

My webhook endpoint is receiving events, but my server keeps timing out. What should I do?

FanBasis expects an HTTP 200 response within a few seconds of delivery. If your handler performs slow operations (database writes, email sends, external API calls), move that work to a background job and return 200 immediately. Example pattern:

  • Receive webhook POST → validate signature → enqueue a background job → return 200
  • Background job processes the event asynchronously

This prevents timeouts and avoids FanBasis retrying the event thinking it failed.

Can I test webhooks without a publicly accessible server?

Yes — use a tunneling tool like ngrok or cloudflared tunnel to expose your local server to the internet during development. Run ngrok, get a public URL (e.g., https://abc123.ngrok.io), register it as your webhook URL in FanBasis, and use the Test Webhook endpoint to fire a simulated event to it.

What happens if FanBasis can't reach my webhook endpoint?

If your endpoint is unreachable or returns a non-200 status, FanBasis will retry delivery several times with exponential backoff. Make sure your endpoint is reliable and returns 200 promptly. If deliveries continue to fail, check the webhook subscription status in your dashboard and look for error logs.

Help & Support

We're here to help you integrate and grow. Choose the support channel that best fits your question.

📧
Email Support
For account questions, billing issues, or anything that requires a detailed response from our team.
support@fanbasis.com

Replies within 24–48 hrs
📖
Documentation
Browse this reference guide for endpoint specs, parameter tables, and code examples for every API operation.
Always available
🐛
Bug Reports
Found something broken? Send us a bug report with the full request, response body, and any relevant error messages.
Report a bug →

Prioritized quickly
🔑
API Key Issues
If you suspect your key is compromised, regenerate it immediately from Settings → Developer in your dashboard — no need to contact support.
Self-service
✦ Getting help faster

When contacting support about an API issue, always include: (1) the full curl command or request you sent, (2) the complete JSON response body, (3) the HTTP status code, and (4) your approximate account email or API key prefix (first 8 characters only — never the full key). This cuts resolution time dramatically.

Pre-Launch Checklist

Before switching from sandbox to production, run through this checklist:

Common Workflows

Step-by-step guides for the most common seller scenarios. Each workflow shows the exact API calls in order, so you can go from zero to production fast.

💳
Collect a One-Time Payment
Sell a product, ticket, or digital download with a single checkout
1
Create a Checkout Session
Pass your product details, price, and a success_url where the buyer lands after payment.
POST /public-api/checkout-sessions
2
Redirect the Buyer
Use the checkout_url from the response to send your buyer to the FanBasis-hosted payment page.
3
Listen for the Webhook
Your server receives a payment.succeeded event confirming the charge. Verify the signature, then fulfill the order.
Webhook: payment.succeeded
4
Deliver the Product
Grant access, send a download link, or ship the item. The transaction is complete and funds are on the way to your balance.
🔄
Set Up a Recurring Subscription
Bill customers weekly, monthly, or yearly — automatically
1
Create a Product with Recurring Billing
Set up your product in the dashboard with a recurring price interval (weekly, monthly, or yearly). Note the product_id.
2
Create a Checkout Session
Pass the product_id and set mode: "subscription". The buyer will see the recurring terms on the checkout page.
POST /public-api/checkout-sessions
3
Handle Subscription Events
Listen for subscription.created to activate access, subscription.renewed for renewals, and subscription.payment_failed to pause access.
Webhook: subscription.created
4
Manage the Subscription
Use the API to cancel, extend, or look up subscriber status anytime. Subscribers can also be managed from the dashboard.
DELETE /public-api/checkout-sessions/:checkoutSessionId/subscriptions/:subscriptionId
🏷️
Run a Discount Campaign
Create promo codes for launches, seasonal sales, or influencer partnerships
1
Create a Discount Code
Set a percentage or fixed-amount discount with optional limits: max uses, expiration date, and minimum purchase amount.
POST /public-api/discount-codes
2
Share the Code
Distribute via email, social media, or embed it directly in your checkout URL as a query parameter. Buyers enter it at checkout.
3
Track Usage
Pull code stats via the API to see how many times it's been redeemed. Update the code or delete it when the campaign is over.
GET /public-api/discount-codes/:discountCodeId
↩️
Issue a Refund
Process full or partial refunds for transactions
1
Find the Transaction
Look up the original transaction by ID or list all transactions for the customer to locate the charge.
GET /public-api/transactions/:transactionId
2
Submit the Refund
Call the refund endpoint with the transaction_id and optionally a partial amount. If omitted, the full amount is refunded.
POST /public-api/checkout-sessions/transactions/:transactionId/refund
3
Confirm via Webhook
A refund.created event fires once the refund is processed. Update your records and notify the customer.
Webhook: refund.created
Set Up Webhook Automation
Get real-time notifications for every payment, subscription, and dispute event
1
Register Your Endpoint
Deploy an HTTPS endpoint on your server and register it via the API or dashboard. Choose which events to subscribe to.
POST /public-api/webhook-subscriptions
2
Implement Signature Verification
Every webhook includes an HMAC-SHA256 signature header. Verify it matches the payload using your webhook secret to prevent spoofing.
3
Return 200 Immediately
Acknowledge the webhook fast — return HTTP 200 before doing heavy work. Queue processing asynchronously to avoid timeouts and retries.
4
Handle Idempotency
Webhooks may be retried. Store the event id and skip duplicates so you never double-process a payment or refund.
✦ Pro tip

Combine these workflows together for powerful automations. For example, create a subscription checkout with a discount code applied, then handle renewals, cancellations, and refunds all through webhooks — fully automated, no manual work needed.

Changelog

A running log of API updates, new features, and breaking changes. Subscribe to webhook event api.changelog (coming soon) to get notified automatically.

February 2026
AI Agent MCP Integration
New
Connect Claude, ChatGPT, or Grok directly to your FanBasis account via MCP. Query transactions, customers, products, and subscribers with natural language. Includes full setup guides for Claude Desktop, ChatGPT, and Grok.
February 2026
API Playground
New
Interactive API explorer built right into the docs. Test any endpoint with your API key, see real responses, and build requests without writing code. Includes request history and environment switching.
February 2026
Common Workflows Guide
New
Step-by-step guides for the five most common seller scenarios: one-time payments, recurring subscriptions, discount campaigns, refunds, and webhook automation.
February 2026
Premium Documentation Redesign
Improved
Complete UI overhaul with dark mode support, multi-language code examples (Shell, Python, Node, PHP), sidebar search, scroll animations, and a new design system inspired by the best API docs in the industry.
January 2026
Embedded Checkout
New
Embed the FanBasis checkout directly on your website using an iframe. No redirects needed — buyers stay on your domain for a seamless purchase experience.
January 2026
Discount Codes API
New
Full CRUD API for discount codes. Create percentage or fixed-amount discounts, set usage limits and expiration dates, and track redemptions programmatically.
January 2026
Webhook Signature Validation
Improved
Enhanced webhook security with HMAC-SHA256 signature verification. Updated docs now include validation examples in Python, Node.js, and PHP.
December 2025
Disputes & Chargebacks
New
New dispute lifecycle documentation covering chargeback notifications, evidence submission deadlines, and webhook events for real-time dispute tracking.
December 2025
Rate Limiting Update
Improved
Rate limits increased to 100 requests per minute across all endpoints. New X-RateLimit-Remaining and X-RateLimit-Reset headers included in every response.
DEVELOPER RESOURCES

SDK Overview

ℹ SDK Info Coming Soon

Full SDK documentation is currently being prepared. Check back soon for complete integration guides, code examples, and language-specific references.

TOOLS

API Playground

Test any FanBasis API endpoint directly from these docs. Enter your API key, pick an endpoint, fill in the parameters, and hit Send. No terminal, no Postman — just click and go.

Sandbox Production ⚠ You're pointing at live data

Choose an Endpoint


    
  
Recent Requests
No requests yet — pick an endpoint above and hit Send.
AI AGENT

Connect an AI Agent

Supercharge your FanBasis workflow with AI. Connect any MCP-compatible AI agent — Claude, ChatGPT, or Grok — directly to your account. Ask questions in natural language and get instant answers about your products, transactions, customers, and subscribers.

Open in AI Assistant
Open in ChatGPT Open in Claude 𝕏 Open in Grok
API Docs for AI
View as Markdown
◆ What can the AI agent do?

Once connected, you can ask things like: "Show me my top-selling products", "How many active subscribers do I have?", "Look up customer jane@example.com", or "What discount codes are currently active?" — and the agent will query your FanBasis data in real time.

🤖 Natural Language Queries
Ask questions in plain English. No API calls, no code, no dashboards — just talk to your data.
🔒 Read-Only & Secure
The agent can only read your data — it cannot modify, delete, or charge anything.
⚡ 11 Built-in Tools
Products, customers, transactions, subscribers, discount codes, checkout sessions — all queryable instantly.
🔑 Your Data Only
Scoped to your API key. You only see your own account data — never another seller's.

FanBasis MCP Connector

Connect your AI directly to your FanBasis account using the Model Context Protocol (MCP). This gives the AI real-time access to query your data and execute actions through 11 built-in tools.

ℹ Where to find your API key

Go to your FanBasis dashboard → Account → API Keys. Copy your live or sandbox key.

Claude Desktop — One-Click MCP Setup

Connect Claude Desktop to your live FanBasis account in seconds. Claude will be able to list products, search customers, view transactions, manage subscriptions, and more — all through natural language.

1
Enter your API key

Your key is only used locally to build the connection URL. It is never stored or sent to any third party.

2
Download your personalized extension

Click below to generate and download a .mcpb extension file with your API key built in.

3
Double-click the downloaded file

Claude Desktop will open and show an install prompt. Click Install and you're done!

4
Start asking questions! Try "Show me all my products" or "List my recent transactions".
Requires Claude Desktop

Make sure you have Claude Desktop installed before opening the extension file. Download Claude Desktop here.

Alternative: Manual config setup

If the extension file doesn't work, you can configure manually:

  1. Click to copy your MCP config
  2. Open Claude Desktop → Settings → Developer → Edit Config
  3. Paste the config, save, and restart Claude Desktop
ChatGPT — MCP Setup Coming Soon

ChatGPT connects to MCP servers via HTTP. Once the FanBasis remote MCP endpoint is deployed:

1
Go to ChatGPT Settings → Apps → Add new app

Paste the server URL: https://www.fanbasis.com/mcp

2
Authorize with your FanBasis account (OAuth login flow)
3
Use in Developer Mode — click + → More → Developer Mode → select FanBasis
⚠ Engineering Note

Requires deployment of a Streamable HTTP MCP server at /mcp with OAuth support.

Grok — MCP Setup (Developer API) Coming Soon

For developers building with the xAI API, add FanBasis as a remote MCP tool:

xAI Responses API
curl https://api.x.ai/v1/responses \
  -H "Authorization: Bearer $XAI_API_KEY" \
  -d '{
  "model": "grok-4-1-fast-reasoning",
  "input": "List my FanBasis products",
  "tools": [{
    "type": "mcp",
    "server_url": "https://www.fanbasis.com/mcp",
    "server_label": "fanbasis",
    "headers": { "x-api-key": "YOUR_KEY" }
  }]
}'
⚠ Engineering Note

Same remote MCP endpoint as ChatGPT. One server powers both platforms.

Available AI Agent Tools

All 11 read-only tools are available across every connected platform:

ToolAPI EndpointDescription
fanbasis_list_productsGET /productsList all products with prices and payment links
fanbasis_list_customersGET /customersSearch customers by name, email, or phone
fanbasis_list_transactionsGET /checkout-sessions/transactionsAll payments, filterable by product or customer
fanbasis_get_transactionGET /transactions/:idSingle transaction details with fees and refunds
fanbasis_list_subscribersGET /subscribersAll subscribers across all products
fanbasis_get_checkout_sessionGET /checkout-sessions/:idCheckout session config and pricing
fanbasis_get_session_transactionsGET /checkout-sessions/:id/transactionsTransactions for a specific session
fanbasis_get_session_subscriptionsGET /checkout-sessions/:id/subscriptionsSubscriptions for a specific session
fanbasis_list_discount_codesGET /discount-codesAll discount codes with usage stats
fanbasis_get_discount_codeGET /discount-codes/:idSingle discount code detail
fanbasis_get_payment_methodsGET /customers/:id/payment-methodsCustomer's saved cards (last 4 only)

Example Conversations

Once connected, try these prompts with any AI agent:

📊 Sales Summary
"Give me a summary of my sales this month"
👤 Customer Lookup
"Look up customer john@example.com and show his purchases"
🏷️ Discount Analysis
"Which discount codes have been used the most?"
🔄 Subscriber Report
"Show me all active subscribers to my Pro Monthly plan"
0%