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?
Key Concepts
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.
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.
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.
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.
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.
{ "status": "success",
"message": "...",
"data": { ... }
}
{ "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.
payment_link — that's your customer's payment page.payment_link URL to your customer via email, SMS, or embed it as a button. When they pay, FanBasis handles the entire checkout experience.payment.succeeded fires. React automatically — grant access, send a welcome email, update your database.Say you sell a $29/month Discord community. Here's the complete flow:
- Create one checkout session —
type: "subscription",frequency_days: 30,amount_cents: 2900. FanBasis returns apayment_link. - 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.
- Customer pays — FanBasis handles the checkout form, card processing, and receipt. You don't touch any of it.
- 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. - Renewals are handled for you — every 30 days FanBasis rebills the subscriber and fires another
payment.succeeded. If a renewal fails,payment.failedfires 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.
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.
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 Brand | Card Number | Expiry | CVV |
|---|---|---|---|
| Visa | 4242 4242 4242 4242 | Any future date | Any 3 digits |
| Mastercard | 5555 5555 5555 4444 | Any future date | Any 3 digits |
| Amex | 3782 822463 10005 | Any future date | Any 4 digits |
| Discover | 6011 1111 1111 1117 | Any future date | Any 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.
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 |
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.
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.
- ✓ 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
- ✗ 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
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.
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.
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.
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:
- → Subscription communities (Discord, Telegram, Slack) — linked from email, your website, or social media
- → Newsletters and online courses sold via a web page or link
- → Digital downloads and software licenses
- → Any offer completed through a FanBasis checkout link shared outside of a native app
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.
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.
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.succeeded | Grant access, send a welcome email, update your database, generate a receipt. |
payment.failed | Email the customer, flag the account, or retry the charge later. |
subscription.created | Create an account for the customer in your system, assign roles or permissions. |
subscription.renewed | Log the renewal, extend access dates in your database. |
subscription.canceled | Revoke access, downgrade the account, trigger a re-engagement campaign. |
product.purchased | Fulfill the order, email a download link, or unlock digital content. |
refund.created | Revoke access, update your records, account for the non-refundable processing fee. |
dispute.created | Alert your team immediately and begin gathering transaction evidence. |
dispute.updated | Check data.status — if won, restore access; if lost, deduct the amount and fee from your records. |
All Event Types
| Event | What it means |
|---|---|
payment.succeeded | A payment was successfully processed and funds are on their way to you. |
payment.failed | A payment attempt was declined or failed. |
payment.expired | A checkout session expired before the customer completed payment. |
payment.canceled | A payment was canceled before it was completed. |
product.purchased | A customer completed a one-time product purchase. |
subscription.created | A customer started a new subscription. |
subscription.renewed | A subscription successfully billed for a new period. |
subscription.completed | A subscription ran through all its billing periods and ended. |
subscription.canceled | A subscription was canceled — either by you or by the customer. |
subscription.payment_failed | A recurring subscription charge failed. |
refund.created | A refund was issued to a customer. |
dispute.created | A chargeback was filed by the customer's bank. |
dispute.updated | A 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
Shows all webhook endpoints you've registered, what events they're listening for, and whether they're active.
curl https://www.fanbasis.com/public-api/webhook-subscriptions \
-H "x-api-key: YOUR_API_KEY"
{
"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
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.
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.
{
"webhook_url": "https://yoursite.com/webhooks/fanbasis",
"event_types": [
"payment.succeeded",
"payment.failed",
"subscription.created",
"subscription.canceled"
]
}
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"]
}'
{
"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
Removes a webhook subscription. FanBasis will immediately stop sending events to that URL.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
webhookSubscriptionId | string | Yes | The ID of the webhook subscription to remove. |
curl -X DELETE "https://www.fanbasis.com/public-api/webhook-subscriptions/ws_abc123" \
-H "x-api-key: YOUR_API_KEY"
{
"status": "success",
"message": "Webhook subscription deleted successfully",
"data": []
}
Test a Webhook Subscription
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
| Parameter | Type | Required | Description |
|---|---|---|---|
webhookSubscriptionId | string | Yes | The ID of the webhook subscription to test. |
{
"event_type": "payment.succeeded"
}
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" }'
{
"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:
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.
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.
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.
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.
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
- Capture the raw request body before any parsing
- Read the signature from the
x-webhook-signatureheader - Compute
HMAC-SHA256(raw_body, your_secret)and hex-encode it - Compare using a constant-time comparison to prevent timing attacks
- Reject with
401 Unauthorizedif signatures do not match
// 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
Standard string equality short-circuits on the first differing byte, leaking timing information. Use hash_equals / timingSafeEqual / hmac.compare_digest instead.
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.
A spike in signature failures could indicate someone probing your endpoint. Log the source IP and timestamp for every rejected request.
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.
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.
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
| Event | Category | When it fires | Format |
|---|---|---|---|
payment.succeeded | Payment | A payment was successfully charged | Flat |
payment.failed | Payment | A payment attempt was declined or errored | Flat |
payment.expired | Payment | A checkout session timed out before payment | Flat |
payment.canceled | Payment | A payment was canceled before completion | Flat |
product.purchased | Product | A one-time product purchase was completed | Flat |
subscription.created | Subscription | A new subscription was started (first payment) | Flat |
subscription.renewed | Subscription | A subscription successfully billed for another period | Flat |
subscription.completed | Subscription | A subscription finished all scheduled payments | Flat |
subscription.canceled | Subscription | A subscription was canceled | Flat |
dispute.created | Dispute | A chargeback was filed by the customer's bank | Envelope |
dispute.updated | Dispute | A dispute's status changed (e.g., resolved as won) | Envelope |
refund.created | Refund | A refund was issued to a customer | Envelope |
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.
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.
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.
{
"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
| Field | Type | Notes |
|---|---|---|
payment_id | string | Unique transaction ID. Use as your idempotency key. May be null for free-trial subscriptions. |
checkout_session_id | integer | The numeric checkout session ID that generated this payment. |
customer_id | string | The FanBasis customer record for the buyer. |
subscription_id | string|null | Set for subscription payments. Null for one-time purchases. |
buyer.id | integer | The buyer's unique user ID. |
buyer.email | string | The buyer's email address. |
buyer.name | string | Full name as entered at checkout. |
buyer.country_code | string | ISO 3166-1 alpha-2 code (e.g. "US", "GB"). |
buyer.ip_address | string | Buyer IP at time of purchase. |
item.id | integer | The purchased item's unique ID. |
item.type | string | One of: subscription, one_time, single_payment, payment_link_subscription. |
item.unit_price | integer | Price in smallest currency unit (cents). 2900 = $29.00. |
amount | integer | Total amount charged, in cents. |
currency | string | ISO 4217 code (e.g. "USD"). |
status | string | Always paid for this event. |
payment_method | string | Payment method used (e.g. card). |
api_metadata | object|null | Key-value pairs you passed when creating the checkout session. Use to pass Discord IDs, plan names, or other context into your webhook handler. |
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
{
"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"
}
}
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.
{
"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
| Field | Type | Notes |
|---|---|---|
payment_id | string | The failed transaction attempt ID. |
customer_id | string | The customer who attempted to pay. |
subscription_id | string|null | Set if this is a failed subscription renewal attempt. |
failure_reason | string | Machine-readable reason: card_declined, insufficient_funds, expired_card, etc. |
status_code | integer | HTTP-style status code from the payment processor. |
status | string | Always failed for this event. |
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
{
"payment_id": "pi_3RLaB2Gb...",
"customer_id": 8421,
"failure_reason": "Your card was declined",
"timestamp": "2025-11-20T21:42:47+00:00"
}
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.
{
"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
| Field | Type | Notes |
|---|---|---|
checkout_session_id | integer | The numeric session ID that expired. |
customer_id | null | Always null — no customer was created since payment never completed. |
subscription_id | null | Always null — no subscription was created. |
expiration_date | string (ISO 8601) | UTC timestamp when the session expired. |
item | object | The product from the expired session — tells you which product the customer was trying to buy. |
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
{
"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"
}
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.
{
"payment_id": "txn_abc123",
"customer_id": "cust_def456",
"failure_reason": "customer_canceled",
"status": "canceled"
}
Field Reference
| Field | Type | Notes |
|---|---|---|
payment_id | string | The canceled transaction ID. |
customer_id | string | The customer who was in the checkout flow. |
failure_reason | string | Why it was canceled. Common values: customer_canceled, api_canceled. |
status | string | Always canceled for this event. |
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
{
"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.
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: 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.
{
"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
| Field | Type | Notes |
|---|---|---|
payment_id | string | The transaction ID for this purchase. |
product_price | integer | Price in cents. 2900 = $29.00. |
buyer | object | Buyer identity: id, email, name, country_code, ip_address. |
buyer.id | integer | The buyer's unique user ID. |
item | object | Product details: id, name, type, description, quantity, unit_price, tax. |
item.id | integer | The purchased item's unique ID. |
additional_params.bump | boolean | True if this purchase originated from an order bump. |
additional_params.upsell | boolean | True if this purchase originated from an upsell offer. |
additional_params.downsell | boolean | True if this purchase originated from a downsell offer. |
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
{
"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.
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.
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.
{
"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
| Field | Type | Notes |
|---|---|---|
subscription.id | string | Unique subscription ID. Store this to link future subscription events. |
subscription.status | string | active for a live subscription. |
subscription.start_date | string (ISO 8601) | When the subscription became active. |
subscription.is_free_trial | boolean | True if this period is a free trial with no charge yet. |
subscription.payment_frequency | string | Billing cadence: daily, weekly, monthly, yearly, or a custom interval. |
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
{
"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"
}
}
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.
{
"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)
| Field | Type | Notes |
|---|---|---|
subscription.renewed_at | string (ISO 8601) | Timestamp of this specific renewal. |
subscription.auto_renew_count | integer | How many times this subscription has auto-renewed. Starts at 1 after the first renewal. |
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
{
"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"
}
}
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.
{
"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)
| Field | Type | Notes |
|---|---|---|
subscription.status | string | completed for this event. |
subscription.completion_reason | string | Why it completed (e.g. all_payments_completed). |
subscription.completed_at | string (ISO 8601) | When the subscription completed. |
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
{
"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"
}
}
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.
{
"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)
| Field | Type | Notes |
|---|---|---|
subscription.status | string | canceled for this event. |
subscription.cancelled_at | string (ISO 8601) | When the subscription was canceled. |
subscription.cancellation_reason | string | Common values: customer_request, api_request, payment_failed. |
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
{
"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.
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.
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.
{
"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
| Field | Type | Notes |
|---|---|---|
id | string (UUID) | Unique event envelope ID — use for idempotency. |
type | string | Always dispute.created. |
created_at | string (ISO 8601) | When the event was generated. |
data.id | integer | Internal FanBasis dispute record ID. |
data.dispute_id | string | Processor-level dispute ID (dp_ prefix). Track this to correlate dispute.updated events. |
data.amount | number | Disputed payment amount in dollars. |
data.dispute_fee | number | Chargeback fee from the payment processor in dollars (typically $15). |
data.total_amount | number | Total financial exposure: amount + dispute_fee. |
data.status | string | needs_response when first created. |
data.reason | string | Bank-provided reason: fraudulent, product_not_received, duplicate, subscription_canceled, etc. |
data.payment_intent_id | string | Processor payment intent ID for the disputed charge. |
data.due_by | string (ISO 8601) | Deadline to submit your response. Do not miss this. |
data.creator_id | integer | Your FanBasis creator/seller account ID. |
data.buyer | object | Disputing customer: id, name, email. |
data.item | object | The disputed product: id, title, type. |
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
{
"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"
}
A dispute has had a status change. The most common transition is needs_response → under_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.
{
"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
| Status | Meaning |
|---|---|
needs_response | Dispute just opened — submit evidence before due_by. |
under_review | You submitted evidence and the bank is reviewing it. |
won | Resolved in your favor — funds and fee returned to you. |
lost | Resolved in the customer's favor — amount and fee deducted. |
warning_needs_response | Early warning — respond quickly to prevent a full chargeback. |
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
{
"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.
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.
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.
{
"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
| Field | Type | Notes |
|---|---|---|
id | string (UUID) | Unique event envelope ID — use for idempotency. |
type | string | Always refund.created. |
created_at | string (ISO 8601) | When the event was generated. |
data.refund_id | integer | Internal FanBasis refund ID. |
data.refund_transaction_id | integer | The original transaction ID that was refunded. |
data.refund_cost | number | Total cost of the refund including processing fees. |
data.refund_cost_creator_amount | number | Amount deducted from the creator's wallet. |
data.amount | number | Amount returned to the buyer. |
data.status | string | success when the refund processed successfully. |
data.refund_type | string | partial or full. |
data.reason | string | Why issued: requested_by_customer, duplicate, fraudulent. |
data.arn | string|null | Acquirer Reference Number, if available from the processor. |
data.stripe.stripe_refund_id | string | Processor-level refund ID (Stripe re_ prefix) for reconciliation. |
data.stripe.stripe_charge_id | string | Processor-level charge/payment intent ID for the original transaction. |
data.stripe.refund_cost_stripe_fee | number | Processing fee forfeited, in dollars. Use for accurate P&L accounting. |
data.buyer | object | Refunded customer ID, name, and email. |
data.item | object | The product that was refunded (id, title, type). |
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
{
"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
Call POST /checkout-sessions with your product details. You receive a checkout_session_id and a payment_link.
Share the link however you like — email, social, your website. FanBasis shows them a secure checkout page.
FanBasis handles everything: card validation, processing, receipts. You don't touch payment data.
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
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.
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.
{
"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"
}
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" }
}'
{
"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"
}
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
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
| Parameter | Type | Required | Description |
|---|---|---|---|
checkoutSessionId | string | Yes | The checkout_session_id returned when you created the session. |
curl "https://www.fanbasis.com/public-api/checkout-sessions/NLxj6" \
-H "x-api-key: YOUR_API_KEY"
{
"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
Permanently deletes a checkout session and deactivates its payment link. Anyone who visits the link afterwards will see a "not found" error.
Existing transactions and subscriptions created through this session are not affected — only the ability to make new purchases is removed.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
checkoutSessionId | string | Yes | The ID of the checkout session to delete. |
curl -X DELETE "https://www.fanbasis.com/public-api/checkout-sessions/NLxj6" \
-H "x-api-key: YOUR_API_KEY"
{ "status": "success", "message": "Checkout session deleted successfully", "data": [] }
Get Transactions for a Checkout Session
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
| Parameter | Type | Required | Description |
|---|---|---|---|
checkoutSessionId | string | Yes | The checkout session ID. |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
page | integer | No | Which page of results to show. Starts at 1. |
per_page | integer | No | How many results per page (max 100). |
curl "https://www.fanbasis.com/public-api/checkout-sessions/NLxj6/transactions?page=1&per_page=20" \
-H "x-api-key: YOUR_API_KEY"
{
"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
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.
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.
{
"product_id": "NLxj6",
"metadata": {
"user_id": "usr_abc123",
"source": "in-app"
}
}
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" } }'
{
"status": "success",
"data": {
"checkout_session_secret": "550e8400-e29b-41d4-a716-446655440000",
"created_at": "2025-01-15T10:00:00Z"
}
}
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.
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
Returns a searchable, paginated list of all your customers — with their total spend, transaction count, and last payment date.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
search | string | No | Type a name, email, or phone number to search for a specific customer. |
page | integer | No | Page number (starts at 1). |
per_page | integer | No | Results per page (max 100). |
curl "https://www.fanbasis.com/public-api/customers?search=jane@example.com" \
-H "x-api-key: YOUR_API_KEY"
{
"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
Shows all payment cards a customer has on file. You'll need the payment method ID to charge them directly.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
customerId | string | Yes | The customer's ID (from the customer list). |
curl "https://www.fanbasis.com/public-api/customers/cust_1/payment-methods" \
-H "x-api-key: YOUR_API_KEY"
{
"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
Charges a customer using a saved payment method. No checkout page needed — the charge happens immediately.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
customerId | string | Yes | The customer's ID. |
{
"payment_method_id": "pm_abc123xyz",
"service_id": "svc_premium_monthly",
"amount_cents": 1999,
"description": "Monthly premium subscription charge",
"metadata": {}
}
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": {}
}'
{
"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
Returns every subscriber across all your products. Filter by customer or product to narrow results.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
product_id | string | No | Show only subscribers to this product. |
customer_id | string | No | Show only subscriptions belonging to this customer. |
status | string | No | Filter by subscription status (e.g. active, cancelled, expired). |
page | integer | No | Page number. |
per_page | integer | No | Results per page (max 100). |
curl "https://www.fanbasis.com/public-api/subscribers?product_id=NLxj6" \
-H "x-api-key: YOUR_API_KEY"
{
"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
Returns all subscriptions created from a specific checkout session. Useful for subscription products that have multiple subscribers through the same session.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
checkoutSessionId | string | Yes | The checkout session ID. |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
page | integer | No | Page number. |
per_page | integer | No | Results per page (max 100). |
curl "https://www.fanbasis.com/public-api/checkout-sessions/NLxj6/subscriptions?page=1" \
-H "x-api-key: YOUR_API_KEY"
{
"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
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
| Parameter | Type | Required | Description |
|---|---|---|---|
productId | string | Yes | The checkout_session_id of the product to get subscribers for. |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
page | integer | No | Page number. |
per_page | integer | No | Results per page (max 100). |
curl "https://www.fanbasis.com/public-api/checkout-sessions/NLxj6/subscriptions?page=1" \
-H "x-api-key: YOUR_API_KEY"
{
"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
Cancels a customer's subscription. They keep access until the end of the current billing period but won't be charged again.
The subscriptionId is the id field returned in the subscription list above — not the customer's user ID.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
checkoutSessionId | string | Yes | The ID of the product the subscription belongs to. |
subscriptionId | string | Yes | The subscription ID from the subscription list (the id field). |
curl -X DELETE \
"https://www.fanbasis.com/public-api/checkout-sessions/NLxj6/subscriptions/sub_xyz789" \
-H "x-api-key: YOUR_API_KEY"
{
"status": "success",
"message": "Subscription cancelled successfully",
"data": { "id": "sub_1", "cancelled_at": "2025-01-15T14:00:00Z", "subscription_status": "cancelled" }
}
Refund a Transaction
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.
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
| Parameter | Type | Required | Description |
|---|---|---|---|
transactionId | string | Yes | The ID of the transaction to refund. |
{
"amount_cents": 1500
}
# 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 '{}'
{
"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
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
| Parameter | Type | Required | Description |
|---|---|---|---|
checkoutSessionId | string | Yes | The ID of the product the subscription belongs to. |
{
"user_id": "usr_abc123",
"duration_days": 30
}
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 }'
{
"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.
"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
Returns all your discount codes. Use the search parameter to quickly find a specific code.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
search | string | No | Filter codes by their code string or description. |
per_page | integer | No | Results per page (max 100). |
curl "https://www.fanbasis.com/public-api/discount-codes" \
-H "x-api-key: YOUR_API_KEY"
{
"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
Creates a new discount code with the settings you define.
{
"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]
}
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
}'
{ "status": "success", "message": "Discount code created successfully", "data": {} }
Get a Discount Code
Fetches the details of one discount code, including how many times it's been used.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The discount code's ID. |
curl "https://www.fanbasis.com/public-api/discount-codes/1" \
-H "x-api-key: YOUR_API_KEY"
{
"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
Updates an existing discount code. Only include the fields you want to change.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The discount code's ID. |
{
"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]
}
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" }'
{ "status": "success", "message": "Discount code updated successfully", "data": {} }
Delete a Discount Code
Deletes a discount code. Customers with active subscriptions already using this code won't be affected — they'll keep their discount.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The discount code's ID. |
curl -X DELETE "https://www.fanbasis.com/public-api/discount-codes/1" \
-H "x-api-key: YOUR_API_KEY"
{ "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
Returns all your products with their titles, prices, and ready-to-use payment links.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
page | integer | No | Page number. |
per_page | integer | No | Results per page (max 100). |
curl "https://www.fanbasis.com/public-api/products" \
-H "x-api-key: YOUR_API_KEY"
{
"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
Returns the full details of a single payment. Useful for customer support lookups, accounting, or building transaction receipts.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
transactionId | string | Yes | The transaction ID. This appears in webhook events and in the transactions list as the id field. |
curl "https://www.fanbasis.com/public-api/transactions/txn_abc123" \
-H "x-api-key: YOUR_API_KEY"
{
"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
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
| Parameter | Type | Required | Description |
|---|---|---|---|
product_id | string | No | Only show transactions for this product. |
customer_id | string | No | Only show transactions from this customer. |
page | integer | No | Which page of results to show. Starts at 1. |
per_page | integer | No | How many results per page (max 100). |
curl "https://www.fanbasis.com/public-api/checkout-sessions/transactions?page=1&per_page=20" \
-H "x-api-key: YOUR_API_KEY"
{
"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:
| Rule | Details |
|---|---|
| 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:
Replace {transactionId} with the transaction ID (returned in the checkout session response or webhook payload).
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
amount | integer | No | Amount to refund in the smallest currency unit (e.g., cents). Omit for a full refund of the remaining amount. |
reason | string | No | Human-readable reason for the refund. Stored on the refund record and may appear in customer communications. |
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"
}
{
"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:
| Status | Meaning | Action |
|---|---|---|
| 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. |
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.
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:
| Status | Meaning |
|---|---|
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:
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:
| Event | When it fires |
|---|---|
dispute.created | A dispute was filed by the customer's bank. Act immediately and submit evidence as soon as possible. |
dispute.updated | The dispute's status changed (e.g., challenged, won, lost, accepted, expired, cancelled). Check data.status to see the new state. |
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_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.
Specific rate limit thresholds vary by account and endpoint. Contact support@fanbasis.com for the limits that apply to your account.
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
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | The page number to retrieve. Starts at 1. |
per_page | integer | 20 | Number of records per page. Maximum value is 100. |
sort | string | created_at | Field to sort by. Supported values vary by endpoint. |
order | string | desc | asc or desc. |
Paginated Response Structure
{
"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
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:
{
"status": "error",
"message": "Validation failed",
"data": [],
"errors": {
"code": ["The code field is required."],
"service_ids": ["At least one service ID is required."]
}
}
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.
{
"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. |
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.
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.
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.
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).
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.
Replies within 24–48 hrs
Prioritized quickly
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:
- Replaced sandbox API key with live API key in all environments
- Webhook endpoint is deployed at a publicly accessible HTTPS URL
- Webhook signature verification is implemented and tested
- Webhook handler returns HTTP 200 immediately (slow work queued async)
- Idempotency logic prevents double-processing duplicate events
- Error handling for 400/401/500 responses is in place
- API key is stored in an environment variable — not hardcoded in source
- Tested end-to-end with a real payment in production (use a small amount)
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.
success_url where the buyer lands after payment.checkout_url from the response to send your buyer to the FanBasis-hosted payment page.payment.succeeded event confirming the charge. Verify the signature, then fulfill the order.product_id.product_id and set mode: "subscription". The buyer will see the recurring terms on the checkout page.subscription.created to activate access, subscription.renewed for renewals, and subscription.payment_failed to pause access.transaction_id and optionally a partial amount. If omitted, the full amount is refunded.refund.created event fires once the refund is processed. Update your records and notify the customer.id and skip duplicates so you never double-process a payment or refund.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.
X-RateLimit-Remaining and X-RateLimit-Reset headers included in every response.SDK Overview
Full SDK documentation is currently being prepared. Check back soon for complete integration guides, code examples, and language-specific references.
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.
Choose an Endpoint
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.
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.
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.
Go to your FanBasis dashboard → Account → API Keys. Copy your live or sandbox key.
Available AI Agent Tools
All 11 read-only tools are available across every connected platform:
| Tool | API Endpoint | Description |
|---|---|---|
fanbasis_list_products | GET /products | List all products with prices and payment links |
fanbasis_list_customers | GET /customers | Search customers by name, email, or phone |
fanbasis_list_transactions | GET /checkout-sessions/transactions | All payments, filterable by product or customer |
fanbasis_get_transaction | GET /transactions/:id | Single transaction details with fees and refunds |
fanbasis_list_subscribers | GET /subscribers | All subscribers across all products |
fanbasis_get_checkout_session | GET /checkout-sessions/:id | Checkout session config and pricing |
fanbasis_get_session_transactions | GET /checkout-sessions/:id/transactions | Transactions for a specific session |
fanbasis_get_session_subscriptions | GET /checkout-sessions/:id/subscriptions | Subscriptions for a specific session |
fanbasis_list_discount_codes | GET /discount-codes | All discount codes with usage stats |
fanbasis_get_discount_code | GET /discount-codes/:id | Single discount code detail |
fanbasis_get_payment_methods | GET /customers/:id/payment-methods | Customer's saved cards (last 4 only) |
Example Conversations
Once connected, try these prompts with any AI agent: