> ## Documentation Index
> Fetch the complete documentation index at: https://documentation.suby.fi/llms.txt
> Use this file to discover all available pages before exploring further.

# Introduction

### What is Suby.fi?

Suby.fi is a payment platform for one-time and subscription payments. Accept credit/debit card payments and cryptocurrency payments, with pricing in USD or EUR.

Whether you're running a SaaS business, online community, or digital product service, Suby provides the infrastructure to handle payments, manage subscriptions, and grant access to your customers automatically.

### Payment Methods

Suby.fi supports three payment configurations depending on your needs:

| Configuration     | Description                                               | Use Case                                              |
| ----------------- | --------------------------------------------------------- | ----------------------------------------------------- |
| **Card only**     | Accept credit/debit card payments                         | Traditional SaaS, digital products                    |
| **Crypto only**   | Accept on-chain payments (USDC, USDT, ETH, SOL, and more) | Web3-native products, global payments without banking |
| **Card + Crypto** | Accept both payment methods on the same product           | Maximum reach, let customers choose how to pay        |

You configure payment methods per product using the `paymentMethods` field: `["CARD"]`, `["CRYPTO"]`, or `["CARD", "CRYPTO"]`.

### Supported Currencies

Products can be priced in **USD** or **EUR**. The currency is set at product creation via the `currency` field.

* **Card payments**: Charged in the product's currency (USD or EUR)
* **Crypto payments**: Converted automatically using real-time oracle rates (Pyth)

### Supported Chains & Assets

| Chain    | Chain ID | Assets          |
| -------- | -------- | --------------- |
| Ethereum | 1        | USDC, USDT, ETH |
| Base     | 8453     | USDC, ETH       |
| Arbitrum | 42161    | USDC, ETH       |
| BSC      | 56       | USDC, USDT, BNB |
| Solana   | 101      | USDC, SOL       |

You can restrict accepted chains and assets per product, or leave them open to accept all available options.

Need a different EVM-compatible chain or token? Additional chains and assets can be added on request, \[contact us]\([https://discord.com/invite/C8dxBm3hpj](https://discord.com/invite/C8dxBm3hpj)).

### Who is this API for?

This API is intended for merchants who want to:

* Accept fiat and/or crypto payments for one-time purchases & subscriptions
* Automate subscription management and renewals
* Integrate payments into their existing web applications
* Receive real-time notifications about payment events via webhooks
* Build custom checkout flows with payment intents

### Getting Started

Before you begin integrating the API, you'll need:

1. **Create a Suby.fi Account**: Sign up at [https://app.suby.fi](https://app.suby.fi)
2. **Generate Your API Key**:
   * Navigate to [Dashboard Settings](https://app.suby.fi/dashboard/settings)
   * Generate your merchant API key
   * Store it securely, it will only be shown once
   * Use this key in the `X-Suby-Api-Key` header for all API requests
3. **Create a Product**:
   * Set up your one-time or subscription products from the dashboard or via the API
   * Configure pricing, currency, payment methods, and accepted assets
   * Note your product IDs for API integration
4. **Set Up Webhooks** (recommended):
   * Configure your webhook URL in the dashboard settings
   * Store your webhook secret securely for signature verification
   * Implement handlers for the events relevant to your use case
5. \*\*Get Approved for Card Payments \*\*(if using card):
   * Navigate to [Card Request](https://app.suby.fi/dashboard/settings)
   * Submit your proof of business (website, description, expected volume)
   * Once approved, you can enable `"CARD"` as a payment method on your products

### Integration Flow

```text theme={null}
1. Create a product (dashboard or API)
2. Initiate a payment → receive a paymentUrl
3. Redirect your customer to the paymentUrl
4. Receive webhooks as the payment progresses
5. Grant access based on webhook events
```

### Customer Identity

Every customer has a stable `customerId`, returned by the `GET /api/customer` endpoints and on every payment/subscription response and webhook. It never changes — **even if the customer later changes their email** — so it is the reliable key for long-term account history.

On `POST /api/payment/create` and `POST /api/subscription/create` you can identify the customer two ways:

| Field           | Behaviour                                                                                                                                                                                               |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `customerId`    | Links the payment to an existing customer by id, regardless of their current email. **Takes precedence over `customerEmail`.** Returns `404` if the id is unknown. Recommended for returning customers. |
| `customerEmail` | A user account is created (or reused by email) and linked to the payment. Use this the first time you see a customer, then store the `customerId` from the response for next time.                      |
| Neither         | The payment is created without a customer. The hosted checkout page prompts the buyer for their email at pay time and links the customer then.                                                          |

When neither is provided, webhooks fired **before** the buyer supplies their email (e.g. `CHECKOUT_INITIATED`) carry `customerId: null` and `customerEmail: null`. Webhooks fired after a successful payment (`CHECKOUT_SUCCESS`, `PAYMENT_SUCCESS`, …) always include both.

`customerFirstName` / `customerLastName` are optional; they only backfill a display name when the customer has none, and are ignored when neither `customerId` nor `customerEmail` is provided.

### Idempotent Refunds

`POST /api/refund/:paymentId` accepts an optional **`Idempotency-Key`** header — any unique string you generate per refund. If a request times out, retry it with the **same key**: the original result is replayed instead of issuing the refund again, and the response carries `Idempotency-Replayed: true`.

```bash theme={null}
curl -X POST https://api.suby.fi/api/refund/pay_abc123 \
  -H "Content-Type: application/json" \
  -H "X-Suby-Api-Key: sk_live_your_key" \
  -H "Idempotency-Key: refund-pay_abc123-01HZ..." \
  -d '{ "amountInCents": 500, "reason": "Customer requested refund" }'
```

This matters most for **partial refunds**, which are otherwise legitimately repeatable — without a key, a retried timeout would refund twice. A full refund is naturally idempotent (a second call returns a "not refundable" error).

Reusing a key with a **different** request body returns `409` (`IDEMPOTENCY_KEY_CONFLICT`). Retrying while the first request is still in flight also returns `409` (`IDEMPOTENCY_REQUEST_IN_PROGRESS`) — retry shortly. The key can also be passed as an `idempotencyKey` body field; the header takes precedence.

### Webhook Events

Suby.fi sends signed webhooks to notify your server about payment lifecycle events. Configure your webhook URL in the dashboard settings.

**Payment events:**

| Event                | When It's Sent                                                                                                                                                                                                                                                                                                                   | Use Case                                                                               |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| `CHECKOUT_INITIATED` | When a customer starts the payment process (selects a payment method)                                                                                                                                                                                                                                                            | Track checkout conversions, send analytics                                             |
| `CHECKOUT_SUCCESS`   | After a successful card checkout (user authorized payment). **Card payments only.**                                                                                                                                                                                                                                              | Grant access for card payments, update order status, send confirmation emails          |
| `PAYMENT_SUCCESS`    | When a crypto payment is confirmed on-chain, or when a card payment is fully settled by the payment provider (sent after `CHECKOUT_SUCCESS`).                                                                                                                                                                                    | Grant access for crypto payments, final confirmation for card payments, reconciliation |
| `PAYMENT_FAILED`     | When a payment fails. For crypto: on-chain failure. For card: settlement failure (can occur after `CHECKOUT_SUCCESS`).                                                                                                                                                                                                           | Notify the customer, handle retries, revoke access if needed                           |
| `PARTIAL_REFUNDED`   | When a card payment is partially refunded and a residual amount still remains (payment stays `PARTIALLY_REFUNDED`). `data.payment.refundedAmountCents` carries the cumulative refunded total. **Card payments only.**                                                                                                            | Update order/accounting, notify customer of the partial refund                         |
| `PAYMENT_REFUNDED`   | When a card payment has been fully refunded. **Card payments only.**                                                                                                                                                                                                                                                             | Revoke access, update order status, notify customer                                    |
| `PAYMENT_CHARGEBACK` | When a card payment is charged back by the customer's bank (payment status becomes `CHARGEBACK`). **Card payments only.**                                                                                                                                                                                                        | Revoke access, flag the order, reconcile the dispute                                   |
| `PAYMENT_SETTLED`    | When one or more merchant payouts are completed and funds have been sent to the merchant's payout account (wallet/bank account). Contains all payouts settled in this event and every payment they include. **Card payments only** — crypto payments settle directly on-chain to the merchant wallet and do not emit this event. | Reconciliation, accounting, confirm funds received                                     |

**Subscription events:**

| Event                   | When It's Sent                                                                       | Use Case                                                |
| ----------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------- |
| `SUBSCRIPTION_CREATED`  | When a subscription becomes active for the first time (first successful payment)     | Grant access, onboard the customer, provision resources |
| `SUBSCRIPTION_RENEWED`  | When an existing subscription is renewed by a new successful payment                 | Extend access, update the renewal date                  |
| `SUBSCRIPTION_PAST_DUE` | When a subscription is within 24h of expiry and no renewal payment has been received | Notify the customer, send reminders                     |
| `SUBSCRIPTION_EXPIRED`  | When a subscription has expired and access is removed                                | Revoke access, update customer status                   |

#### Recommended Access Granting Strategy

| Payment Method | Grant access on    | Revoke access on                                                |
| -------------- | ------------------ | --------------------------------------------------------------- |
| **Card**       | `CHECKOUT_SUCCESS` | `PAYMENT_FAILED`, `PAYMENT_REFUNDED`, or `SUBSCRIPTION_EXPIRED` |
| **Crypto**     | `PAYMENT_SUCCESS`  | `PAYMENT_FAILED` or `SUBSCRIPTION_EXPIRED`                      |

#### Webhook Payload

Every webhook is signed with HMAC-SHA256 for security. The signature is sent in the `X-Webhook-Signature` header.

**Payment webhook:**

```typescript theme={null}
interface PaymentWebhookEvent {
  id: string;                    // Unique event ID (evt_xxx)
  type: "CHECKOUT_INITIATED" | "CHECKOUT_SUCCESS" | "PAYMENT_SUCCESS" | "PAYMENT_FAILED" | "PARTIAL_REFUNDED" | "PAYMENT_REFUNDED" | "PAYMENT_CHARGEBACK" | "PAYMENT_SETTLED";
  createdAt: string;             // ISO 8601 timestamp
  data: {
    payment: {
      id: string;
      status: string;
      subscriptionId: string | null;
      productId: string | null;
      valueUsd: string | null;   // Amount in cents
      priceCents: string | null; // Price in cents (custom price products)
      currency: string | null;   // "USD" or "EUR"
      vatAmountCents: number | null;    // VAT amount in cents (card only)
      totalAmountCents: number | null;  // Total including VAT (card only)
      refundedAmountCents: number | null; // Cumulative refunded amount in cents (card only)
      txHash: string | null;     // Blockchain transaction hash (crypto only)
      source: string | null;     // "CRYPTO" or "FIAT"
      customerId: string | null; // Stable customer id (never changes)
      customerEmail: string | null;
      customerDiscordId: string | null;
      customerTelegramId: string | null;
      customerDiscordUsername: string | null;
      customerTelegramUsername: string | null;
    };
    context: {
      externalRef: string | null;
      metadata: Record<string, any> | null;
      successUrl: string | null;
      cancelUrl: string | null;
    };
  };
}
```

**Payment settled webhook:**

The `PAYMENT_SETTLED` event has a different payload structure since it bundles every merchant payout settled in this event and every payment they include. Each payment carries a `payoutId` so you can link it back to the originating payout.

> **Card payments only.** This event is emitted only for card payouts (funds sent to the merchant's bank account / IBAN / ACH). Crypto payments settle directly on-chain to the merchant wallet and never trigger a `PAYMENT_SETTLED` event.

```typescript theme={null}
interface PaymentSettledWebhookEvent {
  id: string;                    // Unique event ID (evt_xxx)
  type: "PAYMENT_SETTLED";
  createdAt: string;             // ISO 8601 timestamp
  data: {
    payouts: Array<{
      id: string;                // Payout ID
      payoutAmountCents: number; // Total payout amount in cents
      currency: string;          // "USD" or "EUR"
      paidOutAt: string;         // ISO 8601 timestamp
    }>;
    payments: Array<{
      id: string;
      payoutId: string;          // ID of the payout this payment belongs to
      productId: string | null;
      subscriptionId: string | null;
      priceCents: string | null;
      currency: string | null;
      grossAmountCents: number | null;
      merchantNetCents: number | null;
      customerId: string | null; // Stable customer id (never changes)
      customerEmail: string | null;
      customerDiscordId: string | null;
      customerTelegramId: string | null;
      externalRef: string | null;
    }>;
  };
}
```

**Subscription webhook:**

```typescript theme={null}
interface SubscriptionWebhookEvent {
  id: string;                    // Unique event ID (evt_xxx)
  type: "SUBSCRIPTION_CREATED" | "SUBSCRIPTION_RENEWED" | "SUBSCRIPTION_PAST_DUE" | "SUBSCRIPTION_EXPIRED";
  createdAt: string;             // ISO 8601 timestamp
  data: {
    subscription: {
      id: string;
      status: string;
      productId: string;
      expiresAt: string | null;
    };
    lastPayment: {
      id: string;
      status: string;
      priceCents: string | null;
      currency: string | null;
      createdAt: string;
    } | null;
    customer: {
      id: string | null;         // Stable customer id (never changes)
      email: string | null;
      discordId: string | null;
      telegramId: string | null;
      discordUsername: string | null;
      telegramUsername: string | null;
    };
    context: {
      externalRef: string | null;
      metadata: Record<string, any> | null;
    };
  };
}
```

#### Webhook Headers

| Header                | Description                          |
| --------------------- | ------------------------------------ |
| `X-Webhook-Event`     | Event type (e.g. `CHECKOUT_SUCCESS`) |
| `X-Webhook-Timestamp` | Unix timestamp (seconds)             |
| `X-Webhook-Signature` | `v1=<HMAC-SHA256 hex digest>`        |

#### Verifying Webhook Signatures

```typescript theme={null}
const signedPayload = `${timestamp}.${rawBody}`;
const expected = crypto
  .createHmac("sha256", webhookSecret)
  .update(signedPayload)
  .digest("hex");

const isValid = signature === `v1=${expected}`;
```

Reject webhooks with a timestamp older than 5 minutes to prevent replay attacks.

### Sample Webhook Server

We provide a complete example server that demonstrates webhook reception and signature verification:

**Repository**: [https://github.com/hunterlabs-fi/suby-test-server](https://github.com/hunterlabs-fi/suby-test-server)

This example includes:

* Webhook signature verification
* Event handling for all webhook types
* Payment intent creation
* Product management via API
* Express.js + TypeScript implementation

### API Endpoints Overview

| Method   | Endpoint                            | Description                             |
| -------- | ----------------------------------- | --------------------------------------- |
| `POST`   | `/api/product/create`               | Create a product                        |
| `PATCH`  | `/api/product/:productId`           | Update a product                        |
| `GET`    | `/api/product/all`                  | List all products                       |
| `GET`    | `/api/product/:productId`           | Get product by ID                       |
| `POST`   | `/api/payment/create`               | Create a one-time payment               |
| `GET`    | `/api/payment/`                     | List one-time payments                  |
| `GET`    | `/api/payment/:paymentId`           | Get payment by ID                       |
| `POST`   | `/api/subscription/create`          | Create a subscription payment           |
| `GET`    | `/api/subscription/`                | List subscriptions                      |
| `GET`    | `/api/subscription/:subscriptionId` | Get subscription by ID                  |
| `DELETE` | `/api/subscription/:subscriptionId` | Cancel a subscription                   |
| `POST`   | `/api/refund/:paymentId`            | Refund a card payment (full or partial) |
| `POST`   | `/api/discount/create`              | Create a discount code                  |
| `GET`    | `/api/customer`                     | List customers                          |
| `GET`    | `/api/customer/search?email=`       | Find customer by email                  |
| `GET`    | `/api/customer/:customerId`         | Get customer by ID                      |

All endpoints require the `X-Suby-Api-Key` header.

### Quick Example: Create a Payment

```bash theme={null}
# 1. Create a product
curl -X POST https://api.suby.fi/api/product/create \
  -H "Content-Type: application/json" \
  -H "X-Suby-Api-Key: sk_live_your_key" \
  -d '{
    "name": "Pro Plan",
    "priceCents": "999",
    "currency": "EUR",
    "frequencyInDays": 30,
    "platform": "WEB",
    "paymentMethods": ["CARD", "CRYPTO"],
    "acceptedChains": [8453, 42161]
  }'

# 2. Create a payment (one-time product)
curl -X POST https://api.suby.fi/api/payment/create \
  -H "Content-Type: application/json" \
  -H "X-Suby-Api-Key: sk_live_your_key" \
  -d '{
    "productId": "your_product_id",
    "customerEmail": "customer@example.com",
    "externalRef": "order_123",
    "metadata": { "userId": "usr_456" },
    "successUrl": "https://your-app.com/success",
    "cancelUrl": "https://your-app.com/cancel"
  }'

# Or for a subscription product:
# curl -X POST https://api.suby.fi/api/subscription/create \
#   -H "Content-Type: application/json" \
#   -H "X-Suby-Api-Key: sk_live_your_key" \
#   -d '{ "productId": "your_product_id", "customerEmail": "customer@example.com" }'

# Response:
# {
#   "success": true,
#   "data": {
#     "paymentId": "pay_abc123",
#     "paymentUrl": "https://app.suby.fi/pay/pay_abc123"
#   }
# }

# 3. Redirect customer to paymentUrl
# 4. Receive webhooks → grant access
```

### Key Features

* **Flexible Payment Methods**: Card only, crypto only, or both, per product
* **Multi-Currency Pricing**: Price in USD or EUR
* **Multi-Chain Crypto**: Accept payments on Ethereum, Base, Arbitrum, BSC, and Solana
* **Multi-Asset Support**: USDC, USDT, ETH, SOL, and more
* **One-Time & Subscriptions**: Set `frequencyInDays` for recurring, omit for one-time
* **Automatic Renewals**: Recurring subscriptions are handled automatically
* **Webhook Notifications**: Signed webhooks for all payment lifecycle events
* **Secure Authentication**: API key-based authentication with HMAC-SHA256 webhook verification
* **Metadata Support**: Attach custom data to payments for your internal tracking
* **Platform Integrations**: Web, Invoice, Discord and Telegram, with automatic access management
