# AI Integrations (/en/ai-integrations)



import { Bot, ExternalLink, MousePointerClick, Webhook, Shield, CreditCard } from 'lucide-react';

Overview [#overview]

AI integrations help coding assistants add Yolfi payment notifications to a merchant backend. The assistant can inspect the codebase, find existing webhook or payment fulfillment logic, and choose the smallest safe Yolfi integration.

<Callout type="info">
  The AI skill is intentionally separate from this docs page. Open it when you want the assistant-facing instructions only, without the surrounding documentation.
</Callout>

<Card title="Install Yolfi Skill" description="One-command Claude Code install from the public Yolfi marketplace." icon={<MousePointerClick />} href="#install-in-claude-code" cta="Install" horizontal arrow className="border-fd-primary/40 bg-fd-primary/10" />

<Card title="View Webhook Integration Skill" description="Open the raw assistant instructions in a new tab." icon={<ExternalLink />} href="/skill/yolfi-webhook-integration" target="_blank" rel="noreferrer" cta="Open skill" horizontal />

***

Install In Claude Code [#install-in-claude-code]

Copy and run this command once in your terminal:

```bash
claude plugin marketplace add yolfinance/skill && claude plugin marketplace update yolfi && claude plugin install yolfi-webhook-integration@yolfi
```

If Claude Code is already open, run these slash commands instead:

```text
/plugin marketplace add yolfinance/skill
/plugin marketplace update yolfi
/plugin install yolfi-webhook-integration@yolfi
```

The skill is installed from the public Yolfi marketplace repository: `yolfinance/skill`.

***

When To Use It [#when-to-use-it]

Use the Yolfi webhook integration skill when you want an AI coding assistant to:

* Add Yolfi webhooks to an existing backend.
* Reuse a Stripe or Lemon Squeezy webhook flow through Yolfi adapters when safe.
* Add raw-body signature verification for `X-Yolfi-Signature`.
* Preserve existing order, subscription, entitlement, and fulfillment logic.
* Keep integration minimal: signature routing + payload compatibility, not business rewrites.
* Add tests or local checks proving valid webhooks pass and invalid signatures fail.

For adapter mode, the assistant should preserve your existing webhook pipeline first and only adapt the event envelope and auth path.

The skill should not replace the webhook docs. It loads the current docs before implementing so event names, payload shapes, adapter behavior, and signature rules stay sourced from the documentation.

***

Related Resources [#related-resources]

<Cards>
  <Card title="Webhooks" description="Understand Yolfi webhook events, retries, and delivery behavior." icon={<Webhook />} href="/en/webhooks" />

  <Card title="Webhook Signatures" description="Verify incoming webhook requests with the raw body and API key." icon={<Shield />} href="/en/webhooks/webhook-signatures" />

  <Card title="Adapters" description="Reuse compatible Stripe or Lemon Squeezy webhook handlers." icon={<CreditCard />} href="/en/adapters" />

  <Card title="Raw Skill" description="Open the skill instructions without docs navigation or page chrome." icon={<Bot />} href="/skill/yolfi-webhook-integration" target="_blank" rel="noreferrer" />
</Cards>


# Getting Started (/en/getting-started)



import { ArrowRight, Bot } from 'lucide-react';

Quick Start Guide [#quick-start-guide]

<Steps>
  <Step title="Create Your Account">
    Sign up for an account to get access to your dashboard and API keys.  Sign up at: **[https://app.yolfi.com](https://app.yolfi.com)**

    <Callout type="success">
      You’ll receive a confirmation email to verify your account.
    </Callout>
  </Step>

  <Step title="Add Your First Product">
    Choose your billing model and add your product to start accepting payments.
  </Step>

  <Step title="Choose Your Integration Method">
    Select the integration approach that best fits your needs and technical requirements.

    <Tabs items={["No-Code (Fastest)", "Full API Integration"]}>
      <Tab value="no-code-(fastest)">
        **Perfect for getting started quickly:**

        * Create **Payment Links** from your dashboard
        * Share via email, social media, or embed in websites
        * Start accepting payments immediately
      </Tab>

      <Tab value="full-api-integration">
        **Best for advanced, fully customized solutions:**

        * Use our REST API for seamless integration
        * Programmatic access to create invoices, manage payments
        * Deep integration with your backend, workflows, and data models
        * Full support for webhooks, advanced features, and automation
      </Tab>
    </Tabs>
  </Step>
</Steps>

***

What's Next? [#whats-next]

<Cards>
  <Card title="Webhooks" description="Configure payment notifications" icon={<ArrowRight />} href="/en/webhooks" />

  <Card title="AI Integrations" description="Use an AI coding assistant to add webhook handling" icon={<Bot />} href="/en/ai-integrations" />
</Cards>


# Introduction (/en)



import {
  Rocket,
  Webhook,
  Coins,
  ArrowRight,
  Bot,
} from 'lucide-react';

Crypto Payment Platform [#crypto-payment-platform]

<Cards>
  <Card title="Quickstart" description="Get started in minutes" icon={<Rocket />} href="/en/getting-started" />

  <Card title="Accept Payments" description="Understand the payment flow" icon={<Coins />} href="/en/accept-payments" />

  <Card title="Webhooks" description="Receive payment events" icon={<Webhook />} href="/en/webhooks" />

  <Card title="AI Integrations" description="Install webhooks with an AI coding assistant" icon={<Bot />} href="/en/ai-integrations" />
</Cards>

***

Why Crypto Payments? [#why-crypto-payments]

* **Lower fees** - Crypto transactions cost a fraction of credit card processing
* **No chargebacks** - Blockchain transactions are final
* **Instant settlement** - Payments confirmed in seconds to minutes
* **Global access** - Accept payments from anywhere without banking

***

Key Features [#key-features]

<Cards>
  <Card title="Webhook Notifications" description="Get notified instantly when payments are confirmed." icon={<Webhook />} href="/en/webhooks" />

  <Card title="AI Integration Skill" description="Guide AI coding assistants through safe webhook setup." icon={<Bot />} href="/en/ai-integrations" />
</Cards>

***

Getting Started [#getting-started]

<Card title="Quick Start Guide" icon={<ArrowRight />} href="/en/getting-started" horizontal>
  Create your first payment in 5 minutes. Includes sandbox for testing.
</Card>

***

Who Is This For? [#who-is-this-for]

* **SaaS Companies** - Accept crypto for software subscriptions
* **AI APIs** - Monetize AI services with token-based billing
* **Digital Products** - Sell courses, templates, downloads
* **Global Services** - Accept payments from anywhere

***

LLM Access [#llm-access]

* [llms.txt](/llms.txt) - Structured index for AI tools
* [llms-full.txt](/llms-full.txt) - Full documentation context for AI ingestion
* [AI Integrations](/en/ai-integrations) - Assistant-friendly webhook integration workflow


# Accept Payments (/en/accept-payments)



Supported Networks [#supported-networks]

The following blockchain networks are supported:

| Network   |
| :-------- |
| Solana    |
| Arbitrum  |
| BNB Chain |

Supported Cryptocurrencies [#supported-cryptocurrencies]

The following cryptocurrencies are supported:

* USDC
* USDT


# Settlement Wallet (/en/accept-payments/settlement-wallet)



Settlement Details [#settlement-details]

Before accepting payments, you need to configure the blockchains and corresponding cryptocurrencies you wish to accept through the Settlement Settings.

Configuration [#configuration]

To receive payments, you must add a **settlement wallet address** for each blockchain network you activate. You'll also need to specify which supported currencies you wish to accept on each network.

Setup Steps [#setup-steps]

1. **Add Settlement Wallet**: Configure a wallet address for each blockchain network you want to accept payments on
2. **Select Currencies**: Choose which cryptocurrencies to accept on each network
3. **Activate Networks**: Enable the networks where you want to receive payments

Once configured, customers can send payments in any of the supported cryptocurrencies, and funds will be settled to your designated wallet addresses.


# Adapters (/en/adapters)



import { ArrowRight, CreditCard, Box, Repeat, BarChart, Bot } from 'lucide-react';

Overview [#overview]

Webhook adapters help merchants reuse existing payment webhook handlers when adding Yolfi. Instead of receiving the native Yolfi webhook body, your endpoint can receive a Stripe-compatible or Lemon Squeezy-compatible JSON payload.

Adapters convert field names and event names. They do not change webhook authentication: every adapted webhook is still signed by Yolfi and should be verified with `X-Yolfi-Signature`.

<Callout type="info">
  Minimal-change contract: keep the same webhook URL and business handlers; only add Yolfi signature verification/routing and adapter payload handling.
</Callout>

Adapters work best with webhook handlers that process the event body directly. If your existing handler fetches provider objects through the provider API, keep that branch unchanged and use a separate Yolfi endpoint or an existing body-driven handler.

<Card title="Install adapter mode with an AI assistant" icon={<Bot />} href="/en/ai-integrations" horizontal cta="Open AI Integrations" arrow>
  Use the Yolfi webhook integration skill when you want an assistant to reuse an existing Stripe or Lemon Squeezy webhook flow safely.
</Card>

Supported Adapters [#supported-adapters]

| Adapter       | Best for                                                                               |
| :------------ | :------------------------------------------------------------------------------------- |
| Stripe        | Existing handlers for Checkout, PaymentIntent, Charge, Invoice, or Subscription events |
| Lemon Squeezy | Existing handlers for order and subscription lifecycle events                          |

How It Works [#how-it-works]

Yolfi emits a small set of payment lifecycle events. Some providers emit several webhook events for the same lifecycle moment, so an adapter can send multiple provider-compatible payloads for one Yolfi event.

For example, one Yolfi `payment.confirmed` event with the Stripe adapter can send:

* `payment_intent.succeeded`
* `charge.succeeded`
* `checkout.session.completed`

Your endpoint receives each payload as a separate POST request.

Use `X-Yolfi-Event-ID` as the source lifecycle identifier. One source event can intentionally fan out into multiple adapter payloads.

<Card title="Security" icon={<ArrowRight />} href="/en/webhooks/webhook-signatures" horizontal>
  Check Webhook Signatures for verification information.
</Card>

Field Mapping [#field-mapping]

Adapters put values into provider-native fields whenever there is a direct match. Provider-specific metadata is used only for Yolfi or blockchain fields that the provider does not have a native field for, such as:

* Yolfi invoice, paylink, organization, and subscription IDs
* blockchain network and chain ID
* token contract address and symbol
* merchant metadata

Adapter payloads are compatibility payloads, not canonical provider-owned objects.

Setup [#setup]

1. Open organization settings in the Yolfi dashboard.
2. Set your webhook URL.
3. Select an adapter.
4. Save changes.

What should not change:

* Your fulfillment/subscription business logic.
* Your provider webhook verification path for real provider requests.
* Your data model, provider enums, checkout URLs, or plan configuration.


# Lemon Squeezy Adapter (/en/adapters/lemon-squeezy)



Overview [#overview]

The Lemon Squeezy adapter sends a JSON:API-style body with `meta.event_name`, `meta.custom_data`, `data.type`, `data.id`, and `data.attributes`.

Yolfi webhooks are still authenticated with `X-Yolfi-Signature`. The adapter does not emulate Lemon Squeezy signatures.

Event Mapping [#event-mapping]

| Yolfi event                   | Lemon Squeezy-compatible events                       |
| :---------------------------- | :---------------------------------------------------- |
| `invoice.created`             | `order_created` with pending status                   |
| `payment.confirmed` one-time  | `order_created` with paid status                      |
| `payment.confirmed` recurring | `subscription_payment_success`                        |
| `invoice.overdue`             | `order_created` with expired status                   |
| `subscription.overdue`        | `subscription_payment_failed`, `subscription_expired` |
| `subscription.cancelled`      | `subscription_cancelled`                              |

Field Mapping [#field-mapping]

| Yolfi field                  | Lemon Squeezy field                                                 |
| :--------------------------- | :------------------------------------------------------------------ |
| `amountUsd`                  | `subtotal`, `total`, `subtotal_usd`, `total_usd` in cents           |
| `symbol`                     | `currency`; stablecoins map to `USD`                                |
| `customer.email`             | `user_email`                                                        |
| `customer.name`              | `user_name`                                                         |
| `invoiceId`                  | `data.id`, `identifier`, `order_number`, or subscription invoice ID |
| `subscriptionId`             | `subscription_id` or subscription `data.id`                         |
| `customer.clientReferenceId` | `meta.custom_data.client_reference_id`                              |
| `metadata`                   | `meta.custom_data`                                                  |

Yolfi-specific blockchain fields are stored in `meta.custom_data`: `chain_id`, `network`, `token`, and `symbol`.

Example: order_created [#example-order_created]

```json
{
  "meta": {
    "event_name": "order_created",
    "custom_data": {
      "yolfi_invoice_id": "invoice-001",
      "yolfi_paylink_id": "paylink-001",
      "yolfi_org_id": "org-001",
      "client_reference_id": "merchant-customer-001",
      "chain_id": 42161,
      "network": "ARB",
      "token": "0xaf88",
      "symbol": "USDC"
    }
  },
  "data": {
    "id": "invoice-001",
    "type": "orders",
    "attributes": {
      "identifier": "invoice-001",
      "order_number": "invoice-001",
      "user_name": "Customer One",
      "user_email": "customer@example.com",
      "currency": "USD",
      "subtotal": 1234,
      "tax": 0,
      "total": 1234,
      "status": "paid",
      "status_formatted": "Paid"
    }
  }
}
```

Example: subscription_payment_success [#example-subscription_payment_success]

```json
{
  "meta": {
    "event_name": "subscription_payment_success",
    "custom_data": {
      "yolfi_subscription_id": "subscription-001",
      "yolfi_invoice_id": "invoice-001"
    }
  },
  "data": {
    "id": "invoice-001",
    "type": "subscription-invoices",
    "attributes": {
      "subscription_id": "subscription-001",
      "billing_reason": "renewal",
      "currency": "USD",
      "status": "paid",
      "subtotal": 1234,
      "tax": 0,
      "total": 1234
    }
  }
}
```


# Stripe Adapter (/en/adapters/stripe)



Overview [#overview]

The Stripe adapter sends Stripe-style event envelopes with `object: "event"` and the provider object under `data.object`.

Yolfi webhooks are still authenticated with `X-Yolfi-Signature`. The adapter does not send or emulate `Stripe-Signature`.

Use this adapter as a compatibility bridge into existing Stripe-style handlers. Keep your business logic unchanged and only add Yolfi signature routing.

If a Stripe branch calls the Stripe API with object IDs from the webhook, keep that branch unchanged. Adapter IDs are compatibility IDs, so use a separate Yolfi endpoint or an existing body-driven handler instead of refactoring the Stripe branch.

Event Mapping [#event-mapping]

| Yolfi event                   | Stripe-compatible events                                                                    |
| :---------------------------- | :------------------------------------------------------------------------------------------ |
| `invoice.created`             | `checkout.session.created`                                                                  |
| `payment.confirmed` one-time  | `payment_intent.succeeded`, `charge.succeeded`, `checkout.session.completed`                |
| `payment.confirmed` recurring | `payment_intent.succeeded`, `charge.succeeded`, `invoice.paid`, `invoice.payment_succeeded` |
| `invoice.overdue`             | `checkout.session.expired`                                                                  |
| `subscription.overdue`        | `invoice.payment_failed`, `customer.subscription.updated`                                   |
| `subscription.cancelled`      | `customer.subscription.deleted`                                                             |

For recurring success, Stripe can surface both `invoice.paid` and `invoice.payment_succeeded`. If your business logic listens to both, deduplicate by invoice/event key to avoid double provisioning.

Field Mapping [#field-mapping]

| Yolfi field                  | Stripe field                                                                           |
| :--------------------------- | :------------------------------------------------------------------------------------- |
| `amountUsd`                  | primary source for `amount`, `amount_received`, `amount_total`, `amount_paid` in cents |
| `amount`                     | fallback source when `amountUsd` is missing                                            |
| `symbol`                     | `currency`; stablecoins map to `usd`                                                   |
| `customer.email`             | `customer_email`, `receipt_email`, `billing_details.email`                             |
| `customer.name`              | `customer_details.name`, `billing_details.name`                                        |
| `customer.clientReferenceId` | primary source for `client_reference_id`                                               |
| `customerId`                 | fallback source for `client_reference_id` and `customer`                               |
| `paylinkId`                  | `payment_link` and `metadata.yolfi_paylink_id`                                         |
| `invoiceId`                  | generated Stripe-style object IDs and `metadata.yolfi_invoice_id`                      |
| `subscriptionId`             | generated subscription ID and `metadata.yolfi_subscription_id`                         |
| `metadata`                   | Stripe `metadata` for primitive values                                                 |

Yolfi-specific blockchain fields are stored in Stripe `metadata`: `chain_id`, `network`, `token`, and `symbol`. Adapter metadata also includes Yolfi lifecycle IDs such as `yolfi_event_id`, `yolfi_invoice_id`, `yolfi_paylink_id`, and `yolfi_org_id`.

Stripe-style IDs in adapter payloads are generated compatibility IDs and may not be resolvable via Stripe API lookups.

Example: payment_intent.succeeded [#example-payment_intentsucceeded]

```json
{
  "id": "evt_evt_yolfi_001_payment_intent_succeeded",
  "object": "event",
  "api_version": "2025-04-30.basil",
  "created": 1710000000,
  "livemode": false,
  "pending_webhooks": 1,
  "request": {
    "id": null,
    "idempotency_key": null
  },
  "type": "payment_intent.succeeded",
  "data": {
    "object": {
      "id": "pi_yolfi_invoice_001",
      "object": "payment_intent",
      "amount": 1234,
      "amount_received": 1234,
      "currency": "usd",
      "customer": "cus_yolfi_customer_001",
      "latest_charge": "ch_yolfi_invoice_001",
      "receipt_email": "customer@example.com",
      "status": "succeeded",
      "metadata": {
        "yolfi_invoice_id": "invoice-001",
        "yolfi_paylink_id": "paylink-001",
        "yolfi_org_id": "org-001",
        "chain_id": "42161",
        "network": "ARB",
        "token": "0xaf88",
        "symbol": "USDC"
      }
    }
  }
}
```

Example: checkout.session.completed [#example-checkoutsessioncompleted]

```json
{
  "id": "evt_evt_yolfi_001_checkout_session_completed",
  "object": "event",
  "api_version": "2025-04-30.basil",
  "created": 1710000000,
  "livemode": false,
  "pending_webhooks": 1,
  "request": {
    "id": null,
    "idempotency_key": null
  },
  "type": "checkout.session.completed",
  "data": {
    "object": {
      "id": "cs_yolfi_invoice_001",
      "object": "checkout.session",
      "amount_subtotal": 1234,
      "amount_total": 1234,
      "currency": "usd",
      "customer": "cus_yolfi_customer_001",
      "customer_email": "customer@example.com",
      "client_reference_id": "merchant-customer-001",
      "created": 1710000000,
      "expires_at": 1710028800,
      "livemode": false,
      "metadata": {
        "yolfi_event_id": "evt_yolfi_001",
        "yolfi_invoice_id": "invoice-001",
        "yolfi_paylink_id": "paylink-001",
        "yolfi_org_id": "org-001",
        "chain_id": "42161",
        "network": "ARB",
        "token": "0xaf88",
        "symbol": "USDC"
      },
      "mode": "payment",
      "payment_link": "paylink-001",
      "payment_intent": "pi_yolfi_invoice_001",
      "payment_status": "paid",
      "status": "complete",
      "subscription": null,
      "url": null,
      "customer_details": {
        "email": "customer@example.com",
        "name": "Customer One",
        "phone": null
      }
    }
  }
}
```


# Language Support for Payment Links (/en/paylinks/language)



import { Globe } from 'lucide-react';

Overview [#overview]

Payment links automatically support 8 languages. Your customers see the payment page in their browser's preferred language by default.

You can also force a specific language by adding a language code prefix to the URL. This is useful when sharing payment links with customers who prefer a specific language.

How to Specify Language [#how-to-specify-language]

Add the two-letter language code before the payment link ID in the URL:

```
https://pay.yolfi.com/{language}/{paylink-id}
```

**Example** - to show a payment page in Spanish:

```
https://pay.yolfi.com/es/9007724b-9735-4929-a066-7e7e273a2e6d
```

Supported Languages [#supported-languages]

| Code  | Language |
| ----- | -------- |
| en    | English  |
| es    | Español  |
| de    | Deutsch  |
| fr    | Français |
| zh-CN | 简体中文     |
| ru    | Русский  |
| hi    | हिन्दी   |
| tr    | Türkçe   |

Quick Reference [#quick-reference]

<Card icon={<Globe />} title="Tip">
  When sharing payment links, consider adding the customer's preferred language code to provide a better experience. Simply prepend the language code to the URL.
</Card>


# Webhooks (/en/webhooks)



import { Rocket, Shield, Bell, Repeat, CreditCard, CheckCircle, XCircle, Clock, Bot } from 'lucide-react';

Overview [#overview]

Webhooks enable your application to receive real-time notifications whenever payment events occur in your account. Instead of polling our API for payment status, webhooks push events directly to your server the moment something happens.

<Callout type="info">
  Webhooks are the most reliable way to track payment events. Use them to automatically update your system, trigger fulfillment, or send confirmation emails.
</Callout>

<Card title="Add webhooks with an AI assistant" icon={<Bot />} href="/en/ai-integrations" horizontal cta="Open AI Integrations" arrow>
  Use the Yolfi webhook integration skill to help an AI coding assistant wire this into your backend safely.
</Card>

***

How Webhooks Work [#how-webhooks-work]

When a payment-related event occurs in our system, we send an HTTP POST request to your configured webhook URL. Your server should respond with a `200 OK` status to acknowledge receipt.

Event Types [#event-types]

Invoice Events [#invoice-events]

| Event             | Description                                     |
| :---------------- | :---------------------------------------------- |
| `invoice.created` | Payment initiated, invoice created              |
| `invoice.overdue` | Invoice payment deadline passed without payment |

Payment Events [#payment-events]

| Event               | Description                                            |
| :------------------ | :----------------------------------------------------- |
| `payment.confirmed` | Payment confirmed with enough confirmations and amount |

Subscription Events [#subscription-events]

| Event                    | Description                                          |
| :----------------------- | :--------------------------------------------------- |
| `subscription.overdue`   | Subscription renewal deadline passed without payment |
| `subscription.cancelled` | Subscription was cancelled                           |

<Callout type="info">
  Use `payment.confirmed` as your primary fulfillment trigger. This ensures payment is fully confirmed before you deliver service.
</Callout>

***

Quick Setup [#quick-setup]

<Steps>
  <Step title="Configure your webhook URL">
    Set your webhook URL in your organization settings via the API or dashboard.
  </Step>

  <Step title="Verify signatures">
    Use your API key to verify incoming webhook requests are authentic.
  </Step>

  <Step title="Handle events">
    Process the webhook payload and respond with 200 OK quickly.
  </Step>
</Steps>

***

Retry Policy [#retry-policy]

If your server returns a non-2xx status code or times out, we will retry the webhook:

1. **Immediate retry** - First attempt after failure
2. **Second attempt** - 2 minutes after first retry
3. **Final attempt** - 2 minutes after second retry

Duplicate deliveries are expected during retries. Your handler should be idempotent and deduplicate by `X-Yolfi-Event-ID`.

<Callout type="warning">
  After 3 failed attempts, the webhook is marked as failed. Ensure your endpoint is reliable and returns 200 OK quickly.
</Callout>

***

Deadlines [#deadlines]

| Scenario                                   | Deadline |
| :----------------------------------------- | :------- |
| **First payment** (one-time or recurring)  | 1 hour   |
| **Recurring cycles** (after first payment) | 24 hours |

After an invoice deadline, `invoice.overdue` is sent for that invoice. For recurring subscriptions, do not treat `invoice.overdue` as a subscription cancellation by itself; use `subscription.overdue` or `subscription.cancelled` for subscription access changes.

***

Consistent Event Model [#consistent-event-model]

All webhook events follow the same structure and include:

* **Identifier** - `id`, `invoiceId`, `orgId`
* **Amount** - `amount` (final merchant receivable, formatted), `amountUsd` (USD equivalent)
* **Pricing context** - `currency` (source currency), `rate` (payment-token quote)
* **Type** - `paymentType` (`ONE_TIME` or `RECURRING`)
* **Status** - Current state of the invoice/payment
* **Customer** - Customer info for fulfilling orders

***

Related Resources [#related-resources]

<Cards>
  <Card title="Webhook Details" description="Detailed information about each event type and payload structure" icon={<Bell />} href="/en/webhooks/webhook-details" />

  <Card title="Webhook Signatures" description="Learn how to verify webhook authenticity" icon={<Shield />} href="/en/webhooks/webhook-signatures" />

  <Card title="Adapters" description="Send webhook payloads in Stripe or Lemon Squeezy compatible formats" icon={<CreditCard />} href="/en/adapters" />

  <Card title="AI Integrations" description="Use an AI coding assistant to install webhook handling" icon={<Bot />} href="/en/ai-integrations" />
</Cards>


# Webhook Details (/en/webhooks/webhook-details)



Webhook Event Types [#webhook-event-types]

Each webhook event contains a standardized payload with information about what happened. This guide provides detailed information about each event type.

***

Common Payload Structure [#common-payload-structure]

All webhook events follow the same structure:

```json
{
  "id": "evt_abc123",
  "type": "invoice.created",
  "created": 1699900000,
  "livemode": true,
  "data": { ... }
}
```

| Field      | Type    | Description                                     |
| :--------- | :------ | :---------------------------------------------- |
| `id`       | string  | Unique event identifier (format: `evt_` + UUID) |
| `type`     | string  | Event type (e.g., `"invoice.created"`)          |
| `created`  | integer | Unix timestamp when the event was created       |
| `livemode` | boolean | `true` in production, `false` in sandbox        |
| `data`     | object  | The affected object (invoice data directly)     |

***

Invoice Events [#invoice-events]

Invoice Created [#invoice-created]

Triggered when payment is initiated (user clicks pay button).

```json
{
  "id": "evt_abc123",
  "type": "invoice.created",
  "created": 1699900000,
  "livemode": true,
  "data": {
    "paylinkId": "120e3400-e12b-04d4-a716-446417440031",
    "invoiceId": "550e8400-e29b-41d4-a716-446655440000",
    "orgId": "org_def456",
    "chainId": 42161,
    "token": "0xaf88...5831",
    "symbol": "USDC",
    "network": "ARB",
    "paymentType": "ONE_TIME",
    "amount": "1.00",
    "amountUsd": "1.00",
    "currency": "USD",
    "rate": "1.00",
    "status": "pending",
    "expiresAt": "2024-11-15T14:00:00.000Z",
    "createdAt": "2024-11-14T14:00:00.000Z",
    "updatedAt": "2024-11-14T14:00:00.000Z",
    "metadata": {},
    "customer": {
      "email": "customer@example.com",
      "clientReferenceId": "cust_123",
      "name": "John Doe"
    }
  }
}
```

Invoice Overdue [#invoice-overdue]

Triggered when payment deadline passes without payment.

This event is scoped to a single invoice/payment attempt. It does not mean an existing recurring subscription should be cancelled or access should be revoked. For subscription access changes, handle `subscription.overdue` and `subscription.cancelled`.

```json
{
  "id": "evt_abc123",
  "object": "event",
  "type": "invoice.overdue",
  "created": 1699900000,
  "livemode": true,
  "data": {
    "paylinkId": "120e3400-e12b-04d4-a716-446417440031",
    "invoiceId": "550e8400-e29b-41d4-a716-446655440000",
    "orgId": "org_def456",
    "chainId": 42161,
    "token": "0xaf88...5831",
    "symbol": "USDC",
    "network": "ARB",
    "paymentType": "RECURRING",
    "amount": "1.00",
    "amountUsd": "1.00",
    "currency": "USD",
    "rate": "1.00",
    "status": "expired",
    "expiresAt": "2024-11-15T14:00:00.000Z",
    "createdAt": "2024-11-14T14:00:00.000Z",
    "updatedAt": "2024-11-15T14:01:00.000Z",
    "metadata": {},
    "customer": {
      "email": "customer@example.com",
      "clientReferenceId": "cust_123",
      "name": "John Doe"
    }
  }
}
```

Subscription Cancelled [#subscription-cancelled]

Triggered when a subscription is cancelled.

```json
{
  "id": "evt_abc123",
  "object": "event",
  "type": "subscription.cancelled",
  "created": 1699900000,
  "livemode": true,
  "data": {
    "subscriptionId": "sub_abc123",
    "status": "cancelled",
    "expiresAt": "2024-11-15T14:00:00.000Z",
    "recurringInterval": "MONTH",
    "productName": "Premium Plan",
    "orgId": "org_def456",
    "organizationName": "Example Store",
    "customerId": "cust_123",
    "paylinkId": "120e3400-e12b-04d4-a716-446417440031",
    "invoiceId": "550e8400-e29b-41d4-a716-446655440000",
    "chainId": 42161,
    "token": "0xaf88...5831",
    "symbol": "USDC",
    "network": "ARB",
    "paymentType": "RECURRING",
    "amount": "1.00",
    "amountUsd": "1.00",
    "currency": "USD",
    "rate": "1.00",
    "createdAt": "2024-11-14T14:00:00.000Z",
    "updatedAt": "2024-11-14T15:00:00.000Z",
    "metadata": {},
    "customer": {
      "email": "customer@example.com",
      "clientReferenceId": "cust_123",
      "name": "John Doe"
    }
  }
}
```

***

Payment Events [#payment-events]

Payment Confirmed [#payment-confirmed]

Triggered when payment has received enough blockchain confirmations to be considered final.

```json
{
  "id": "evt_abc123",
  "object": "event",
  "type": "payment.confirmed",
  "created": 1699900000,
  "livemode": true,
  "data": {
    "paylinkId": "120e3400-e12b-04d4-a716-446417440031",
    "invoiceId": "550e8400-e29b-41d4-a716-446655440000",
    "orgId": "org_def456",
    "chainId": 42161,
    "token": "0xaf88...5831",
    "symbol": "USDC",
    "network": "ARB",
    "paymentType": "RECURRING",
    "amount": "1.00",
    "amountUsd": "1.00",
    "currency": "USD",
    "rate": "1.00",
    "status": "confirmed",
    "expiresAt": "2024-11-15T14:00:00.000Z",
    "createdAt": "2024-11-14T14:00:00.000Z",
    "updatedAt": "2024-11-14T15:00:00.000Z",
    "metadata": {},
    "customer": {
      "email": "customer@example.com",
      "clientReferenceId": "cust_123",
      "name": "John Doe"
    }
  }
}
```

***

Subscription Events [#subscription-events]

Subscription Overdue [#subscription-overdue]

Triggered when a subscription expires without payment renewal.

This event is subscription-scoped. Use it when your app should mark an existing subscription as past due or inactive according to your access policy.

```json
{
  "id": "evt_abc123",
  "type": "subscription.overdue",
  "created": 1699900000,
  "livemode": true,
  "data": {
    "subscriptionId": "550e8400-e29b-41d4-a716-446655440000",
    "status": "overdue",
    "expiresAt": "2024-11-15T14:00:00.000Z",
    "recurringInterval": "MONTH",
    "productName": "Premium Plan",
    "orgId": "org_def456",
    "organizationName": "Acme Corp",
    "customerId": "550e8400-e29b-41d4-a716-446655440099",
    "paylinkId": "120e3400-e12b-04d4-a716-446417440031",
    "invoiceId": "550e8400-e29b-41d4-a716-446655440001",
    "chainId": 42161,
    "token": "0xaf88...5831",
    "symbol": "USDC",
    "network": "ARB",
    "paymentType": "RECURRING",
    "amount": "1.00",
    "amountUsd": "1.00",
    "createdAt": "2024-11-01T14:00:00.000Z",
    "updatedAt": "2024-11-15T14:01:00.000Z",
    "metadata": {},
    "customer": {
      "email": "customer@example.com",
      "name": "John Doe"
    }
  }
}
```

***

Handling Recurring Customers [#handling-recurring-customers]

When you receive `invoice.created` for a recurring customer:

**Important:** Use `clientReferenceId` or `email` to identify existing customers:

* If `clientReferenceId` or `email` already exists in your database → update existing customer (don't create new)
* If new → create new customer

The same customer may have multiple invoices over time - always use `clientReferenceId` or `email` to identify them.

***

Object Field Reference [#object-field-reference]

Invoice Object [#invoice-object]

| Field         | Type    | Description                                                              |
| :------------ | :------ | :----------------------------------------------------------------------- |
| `paylinkId`   | string  | Paylink identifier                                                       |
| `invoiceId`   | string  | Invoice identifier (UUID)                                                |
| `orgId`       | string  | Organization ID                                                          |
| `chainId`     | integer | Blockchain chain ID                                                      |
| `token`       | string  | Token contract address                                                   |
| `symbol`      | string  | Token symbol (e.g., "USDC")                                              |
| `network`     | string  | Network name (e.g., "ARB")                                               |
| `paymentType` | string  | `"ONE_TIME"` or `"RECURRING"`                                            |
| `amount`      | string  | Final merchant receivable in the payment token, formatted (e.g., "1.00") |
| `amountUsd`   | string  | USD equivalent of the final merchant receivable (e.g., "1.00")           |
| `currency`    | string  | Original paylink/source currency (e.g., "USD", "EUR", "CNY")             |
| `rate`        | string  | FX quote used for pricing: one payment token in the source currency      |
| `status`      | string  | Current invoice status                                                   |
| `expiresAt`   | string  | Expiration timestamp (ISO 8601)                                          |
| `createdAt`   | string  | Creation timestamp (ISO 8601)                                            |
| `updatedAt`   | string  | Last update timestamp (ISO 8601)                                         |
| `customerId`  | string  | Customer identifier (UUID, optional)                                     |
| `metadata`    | object  | Custom metadata                                                          |
| `customer`    | object  | Customer information                                                     |

Payment Object [#payment-object]

| Field         | Type          | Description                                                              |
| :------------ | :------------ | :----------------------------------------------------------------------- |
| `paylinkId`   | string        | Paylink identifier                                                       |
| `invoiceId`   | string        | Associated invoice ID                                                    |
| `orgId`       | string        | Organization ID                                                          |
| `chainId`     | integer       | Blockchain chain ID                                                      |
| `token`       | string        | Token contract address                                                   |
| `symbol`      | string        | Token symbol (e.g., "USDC")                                              |
| `network`     | string        | Network name (e.g., "ARB")                                               |
| `paymentType` | string        | `"ONE_TIME"` or `"RECURRING"`                                            |
| `amount`      | string        | Final merchant receivable in the payment token, formatted (e.g., "1.00") |
| `amountUsd`   | string        | USD equivalent of the final merchant receivable (e.g., "1.00")           |
| `currency`    | string        | Original paylink/source currency (e.g., "USD", "EUR", "CNY")             |
| `rate`        | string        | FX quote used for pricing: one payment token in the source currency      |
| `status`      | `"confirmed"` | Payment status                                                           |
| `expiresAt`   | string        | Expiration timestamp                                                     |
| `createdAt`   | string        | Creation timestamp                                                       |
| `updatedAt`   | string        | Last update timestamp                                                    |
| `customerId`  | string        | Customer identifier (UUID, optional)                                     |
| `metadata`    | object        | Custom metadata                                                          |
| `customer`    | object        | Customer information                                                     |

Customer Object [#customer-object]

| Field               | Type   | Description                        |
| :------------------ | :----- | :--------------------------------- |
| `email`             | string | Customer email address             |
| `clientReferenceId` | string | Your internal customer reference   |
| `name`              | string | Customer name (if provided)        |
| `phone`             | string | Customer phone (if provided)       |
| `dateOfBirth`       | string | Customer dateOfBirth (if provided) |
| `address`           | string | Customer address (if provided)     |

Subscription Object [#subscription-object]

| Field               | Type    | Description                                                              |
| :------------------ | :------ | :----------------------------------------------------------------------- |
| `subscriptionId`    | string  | Subscription identifier (UUID)                                           |
| `status`            | string  | Subscription status (e.g., "overdue")                                    |
| `expiresAt`         | string  | Expiration timestamp (ISO 8601)                                          |
| `recurringInterval` | string  | Recurring interval (e.g., "MONTH")                                       |
| `productName`       | string  | Product/service name                                                     |
| `orgId`             | string  | Organization ID                                                          |
| `organizationName`  | string  | Organization name                                                        |
| `customerId`        | string  | Customer identifier                                                      |
| `paylinkId`         | string  | Paylink identifier                                                       |
| `invoiceId`         | string  | Associated invoice ID                                                    |
| `chainId`           | integer | Blockchain chain ID                                                      |
| `token`             | string  | Token contract address                                                   |
| `symbol`            | string  | Token symbol (e.g., "USDC")                                              |
| `network`           | string  | Network name (e.g., "ARB")                                               |
| `paymentType`       | string  | `"ONE_TIME"` or `"RECURRING"`                                            |
| `amount`            | string  | Final merchant receivable in the payment token, formatted (e.g., "1.00") |
| `amountUsd`         | string  | USD equivalent of the final merchant receivable (e.g., "1.00")           |
| `currency`          | string  | Original paylink/source currency (e.g., "USD", "EUR", "CNY")             |
| `rate`              | string  | FX quote used for pricing: one payment token in the source currency      |
| `createdAt`         | string  | Subscription creation timestamp                                          |
| `updatedAt`         | string  | Last update timestamp                                                    |
| `metadata`          | object  | Custom metadata                                                          |
| `customer`          | object  | Customer information                                                     |


# Webhook Signatures (/en/webhooks/webhook-signatures)



import { Shield, Lock, AlertTriangle } from 'lucide-react';

Overview [#overview]

Every webhook request includes a signature in the header that you can verify to ensure the request genuinely came from Yolfi and wasn't tampered with during transit.

<Callout type="warning">
  Always verify webhook signatures before processing any webhook event. Failing to do so could allow attackers to send fake payment notifications to your system.
</Callout>

***

Why Verify Signatures? [#why-verify-signatures]

Signature verification provides two key security guarantees:

1. **Authenticity** - Confirms the webhook was sent by Yolfi, not an imposter
2. **Integrity** - Ensures the payload wasn't modified in transit
   Replay/deduplication is an application concern: use `X-Yolfi-Event-ID` with your idempotency pattern to avoid duplicate side effects.

***

How It Works [#how-it-works]

Signature Generation [#signature-generation]

When we send a webhook, we:

1. Create a JSON payload with the event data
2. Generate an HMAC-SHA256 signature using your API key as the secret
3. Encode the signature in Base64
4. Send it in the request headers

Headers [#headers]

Each webhook request includes these headers:

| Header              | Description                          |
| :------------------ | :----------------------------------- |
| `Content-Type`      | Always `application/json`            |
| `X-Yolfi-Signature` | Base64-encoded HMAC-SHA256 signature |
| `X-Yolfi-Event-ID`  | Unique event identifier              |

Verification Process [#verification-process]

To verify a webhook signature:

<Steps>
  <Step title="Get the signature">
    Extract the `X-Yolfi-Signature` header from the request.
  </Step>

  <Step title="Get your API key">
    Retrieve your API key from your organization settings.
  </Step>

  <Step title="Compute expected signature">
    Calculate HMAC-SHA256 of the raw request body using your API key.
  </Step>

  <Step title="Compare signatures">
    Use constant-time comparison to compare your computed signature with the one in the header.
  </Step>
</Steps>

***

Code Examples [#code-examples]

<Tabs items={["Node.js", "Python", "Go"]}>
  <Tab value="Node.js">
    ```javascript
    const crypto = require('crypto');

    function verifyWebhookSignature(payload, signature, apiKey) {
      if (!signature || !apiKey) {
        return false;
      }

      const expected = crypto
        .createHmac('sha256', apiKey)
        .update(payload, 'utf8')
        .digest('base64');

      try {
        return crypto.timingSafeEqual(
          Buffer.from(signature, 'base64'),
          Buffer.from(expected, 'base64')
        );
      } catch (e) {
        return false;
      }
    }

    // Express.js example
    app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
      const signature = req.headers['x-yolfi-signature'];
      const payload = req.body.toString(); // Keep raw body as string
      const apiKey = process.env.YOLFI_API_KEY;

      const isValid = verifyWebhookSignature(payload, signature, apiKey);

      if (!isValid) {
        return res.status(400).send('Invalid signature');
      }

      const event = JSON.parse(payload);
      console.log('Received webhook:', event.type);

      // Process the event
      res.status(200).send('OK');
    });
    ```
  </Tab>

  <Tab value="Python">
    ```python
    import hmac
    import hashlib
    import base64
    import os

    def verify_signature(payload: str, signature: str, api_key: str) -> bool:
        if not signature or not api_key:
            return False

        expected = hmac.new(
            api_key.encode('utf-8'),
            payload.encode('utf-8'),
            hashlib.sha256
        ).digest()

        return hmac.compare_digest(
            base64.b64decode(signature),
            expected
        )

    # Flask example
    @app.route('/webhook', methods=['POST'])
    def webhook():
        signature = request.headers.get('X-Yolfi-Signature')
        payload = request.get_data(as_text=True)
        api_key = os.environ.get('YOLFI_API_KEY')

        if not verify_signature(payload, signature, api_key):
            return 'Invalid signature', 400

        event = request.get_json()
        print(f"Received webhook: {event['type']}")
        return 'OK', 200
    ```
  </Tab>

  <Tab value="Go">
    ```go
    import (
        "crypto/hmac"
        "crypto/sha256"
        "crypto/subtle"
        "encoding/base64"
        "os"
    )

    func verifySignature(payload string, signature string, apiKey string) bool {
        if signature == "" || apiKey == "" {
            return false
        }

        expected := hmac.New(sha256.New, []byte(apiKey))
        expected.Write([]byte(payload))

        expectedSig, _ := base64.StdEncoding.DecodeString(expected.Sum(nil))
        sig, _ := base64.StdEncoding.DecodeString(signature)

        return subtle.ConstantTimeCompare(expectedSig, sig) == 1
    }

    // Echo framework example
    e.POST("/webhook", func(c echo.Context) error {
        signature := c.Request().Header.Get("X-Yolfi-Signature")
        payload, _ := io.ReadAll(c.Request().Body)
        apiKey := os.Getenv("YOLFI_API_KEY")

        if !verifySignature(string(payload), signature, apiKey) {
            return c.String(400, "Invalid signature")
        }

        var event map[string]interface{}
        json.Unmarshal(payload, &event)
        fmt.Printf("Received webhook: %v\n", event["type"])

        return c.String(200, "OK")
    })
    ```
  </Tab>
</Tabs>

***

Security Best Practices [#security-best-practices]

<Steps>
  <Step title="Use timing-safe comparison">
    Always use constant-time comparison functions (`crypto.timingSafeEqual` in Node.js, `hmac.compare_digest` in Python) to prevent timing attacks.
  </Step>

  <Step title="Store API keys securely">
    Keep your API keys in environment variables or a secure secrets manager. Never commit them to version control.
  </Step>

  <Step title="Verify the entire payload">
    Compute the signature over the raw request body, not a parsed/re-serialized version.
  </Step>

  <Step title="Respond quickly">
    Verify the signature and return 200 OK immediately. Process the event asynchronously afterward.
  </Step>

  <Step title="Handle duplicates">
    Webhooks may be retried on failure. Design your handler to be idempotent and deduplicate using `X-Yolfi-Event-ID`.
  </Step>
</Steps>


# AI Integrations (/zh-CN/ai-integrations)



import { Bot, ExternalLink, MousePointerClick, Webhook, Shield, CreditCard } from 'lucide-react';

Overview [#overview]

AI integrations help coding assistants add Yolfi payment notifications to a merchant backend. The assistant can inspect the codebase, find existing webhook or payment fulfillment logic, and choose the smallest safe Yolfi integration.

<Callout type="info">
  The AI skill is intentionally separate from this docs page. Open it when you want the assistant-facing instructions only, without the surrounding documentation.
</Callout>

<Card title="Install Yolfi Skill" description="One-command Claude Code install from the public Yolfi marketplace." icon={<MousePointerClick />} href="#install-in-claude-code" cta="Install" horizontal arrow className="border-fd-primary/40 bg-fd-primary/10" />

<Card title="View Webhook Integration Skill" description="Open the raw assistant instructions in a new tab." icon={<ExternalLink />} href="/skill/yolfi-webhook-integration" target="_blank" rel="noreferrer" cta="Open skill" horizontal />

***

Install In Claude Code [#install-in-claude-code]

Copy and run this command once in your terminal:

```bash
claude plugin marketplace add yolfinance/skill && claude plugin marketplace update yolfi && claude plugin install yolfi-webhook-integration@yolfi
```

If Claude Code is already open, run these slash commands instead:

```text
/plugin marketplace add yolfinance/skill
/plugin marketplace update yolfi
/plugin install yolfi-webhook-integration@yolfi
```

The skill is installed from the public Yolfi marketplace repository: `yolfinance/skill`.

***

When To Use It [#when-to-use-it]

Use the Yolfi webhook integration skill when you want an AI coding assistant to:

* Add Yolfi webhooks to an existing backend.
* Reuse a Stripe or Lemon Squeezy webhook flow through Yolfi adapters when safe.
* Add raw-body signature verification for `X-Yolfi-Signature`.
* Preserve existing order, subscription, entitlement, and fulfillment logic.
* Keep integration minimal: signature routing + payload compatibility, not business rewrites.
* Add tests or local checks proving valid webhooks pass and invalid signatures fail.

For adapter mode, the assistant should preserve your existing webhook pipeline first and only adapt the event envelope and auth path.

The skill should not replace the webhook docs. It loads the current docs before implementing so event names, payload shapes, adapter behavior, and signature rules stay sourced from the documentation.

***

Related Resources [#related-resources]

<Cards>
  <Card title="Webhooks" description="Understand Yolfi webhook events, retries, and delivery behavior." icon={<Webhook />} href="/en/webhooks" />

  <Card title="Webhook Signatures" description="Verify incoming webhook requests with the raw body and API key." icon={<Shield />} href="/en/webhooks/webhook-signatures" />

  <Card title="Adapters" description="Reuse compatible Stripe or Lemon Squeezy webhook handlers." icon={<CreditCard />} href="/en/adapters" />

  <Card title="Raw Skill" description="Open the skill instructions without docs navigation or page chrome." icon={<Bot />} href="/skill/yolfi-webhook-integration" target="_blank" rel="noreferrer" />
</Cards>


# Getting Started (/zh-CN/getting-started)



import { ArrowRight, Bot } from 'lucide-react';

Quick Start Guide [#quick-start-guide]

<Steps>
  <Step title="Create Your Account">
    Sign up for an account to get access to your dashboard and API keys.  Sign up at: **[https://app.yolfi.com](https://app.yolfi.com)**

    <Callout type="success">
      You’ll receive a confirmation email to verify your account.
    </Callout>
  </Step>

  <Step title="Add Your First Product">
    Choose your billing model and add your product to start accepting payments.
  </Step>

  <Step title="Choose Your Integration Method">
    Select the integration approach that best fits your needs and technical requirements.

    <Tabs items={["No-Code (Fastest)", "Full API Integration"]}>
      <Tab value="no-code-(fastest)">
        **Perfect for getting started quickly:**

        * Create **Payment Links** from your dashboard
        * Share via email, social media, or embed in websites
        * Start accepting payments immediately
      </Tab>

      <Tab value="full-api-integration">
        **Best for advanced, fully customized solutions:**

        * Use our REST API for seamless integration
        * Programmatic access to create invoices, manage payments
        * Deep integration with your backend, workflows, and data models
        * Full support for webhooks, advanced features, and automation
      </Tab>
    </Tabs>
  </Step>
</Steps>

***

What's Next? [#whats-next]

<Cards>
  <Card title="Webhooks" description="Configure payment notifications" icon={<ArrowRight />} href="/en/webhooks" />

  <Card title="AI Integrations" description="Use an AI coding assistant to add webhook handling" icon={<Bot />} href="/en/ai-integrations" />
</Cards>


# Introduction (/zh-CN)



import {
  Rocket,
  Webhook,
  Coins,
  ArrowRight,
  Bot,
} from 'lucide-react';

Crypto Payment Platform [#crypto-payment-platform]

<Cards>
  <Card title="Quickstart" description="Get started in minutes" icon={<Rocket />} href="/en/getting-started" />

  <Card title="Accept Payments" description="Understand the payment flow" icon={<Coins />} href="/en/accept-payments" />

  <Card title="Webhooks" description="Receive payment events" icon={<Webhook />} href="/en/webhooks" />

  <Card title="AI Integrations" description="Install webhooks with an AI coding assistant" icon={<Bot />} href="/en/ai-integrations" />
</Cards>

***

Why Crypto Payments? [#why-crypto-payments]

* **Lower fees** - Crypto transactions cost a fraction of credit card processing
* **No chargebacks** - Blockchain transactions are final
* **Instant settlement** - Payments confirmed in seconds to minutes
* **Global access** - Accept payments from anywhere without banking

***

Key Features [#key-features]

<Cards>
  <Card title="Webhook Notifications" description="Get notified instantly when payments are confirmed." icon={<Webhook />} href="/en/webhooks" />

  <Card title="AI Integration Skill" description="Guide AI coding assistants through safe webhook setup." icon={<Bot />} href="/en/ai-integrations" />
</Cards>

***

Getting Started [#getting-started]

<Card title="Quick Start Guide" icon={<ArrowRight />} href="/en/getting-started" horizontal>
  Create your first payment in 5 minutes. Includes sandbox for testing.
</Card>

***

Who Is This For? [#who-is-this-for]

* **SaaS Companies** - Accept crypto for software subscriptions
* **AI APIs** - Monetize AI services with token-based billing
* **Digital Products** - Sell courses, templates, downloads
* **Global Services** - Accept payments from anywhere

***

LLM Access [#llm-access]

* [llms.txt](/llms.txt) - Structured index for AI tools
* [llms-full.txt](/llms-full.txt) - Full documentation context for AI ingestion
* [AI Integrations](/en/ai-integrations) - Assistant-friendly webhook integration workflow


# Accept Payments (/zh-CN/accept-payments)



Supported Networks [#supported-networks]

The following blockchain networks are supported:

| Network   |
| :-------- |
| Solana    |
| Arbitrum  |
| BNB Chain |

Supported Cryptocurrencies [#supported-cryptocurrencies]

The following cryptocurrencies are supported:

* USDC
* USDT


# Settlement Wallet (/zh-CN/accept-payments/settlement-wallet)



Settlement Details [#settlement-details]

Before accepting payments, you need to configure the blockchains and corresponding cryptocurrencies you wish to accept through the Settlement Settings.

Configuration [#configuration]

To receive payments, you must add a **settlement wallet address** for each blockchain network you activate. You'll also need to specify which supported currencies you wish to accept on each network.

Setup Steps [#setup-steps]

1. **Add Settlement Wallet**: Configure a wallet address for each blockchain network you want to accept payments on
2. **Select Currencies**: Choose which cryptocurrencies to accept on each network
3. **Activate Networks**: Enable the networks where you want to receive payments

Once configured, customers can send payments in any of the supported cryptocurrencies, and funds will be settled to your designated wallet addresses.


# Adapters (/zh-CN/adapters)



import { ArrowRight, CreditCard, Box, Repeat, BarChart, Bot } from 'lucide-react';

Overview [#overview]

Webhook adapters help merchants reuse existing payment webhook handlers when adding Yolfi. Instead of receiving the native Yolfi webhook body, your endpoint can receive a Stripe-compatible or Lemon Squeezy-compatible JSON payload.

Adapters convert field names and event names. They do not change webhook authentication: every adapted webhook is still signed by Yolfi and should be verified with `X-Yolfi-Signature`.

<Callout type="info">
  Minimal-change contract: keep the same webhook URL and business handlers; only add Yolfi signature verification/routing and adapter payload handling.
</Callout>

Adapters work best with webhook handlers that process the event body directly. If your existing handler fetches provider objects through the provider API, keep that branch unchanged and use a separate Yolfi endpoint or an existing body-driven handler.

<Card title="Install adapter mode with an AI assistant" icon={<Bot />} href="/en/ai-integrations" horizontal cta="Open AI Integrations" arrow>
  Use the Yolfi webhook integration skill when you want an assistant to reuse an existing Stripe or Lemon Squeezy webhook flow safely.
</Card>

Supported Adapters [#supported-adapters]

| Adapter       | Best for                                                                               |
| :------------ | :------------------------------------------------------------------------------------- |
| Stripe        | Existing handlers for Checkout, PaymentIntent, Charge, Invoice, or Subscription events |
| Lemon Squeezy | Existing handlers for order and subscription lifecycle events                          |

How It Works [#how-it-works]

Yolfi emits a small set of payment lifecycle events. Some providers emit several webhook events for the same lifecycle moment, so an adapter can send multiple provider-compatible payloads for one Yolfi event.

For example, one Yolfi `payment.confirmed` event with the Stripe adapter can send:

* `payment_intent.succeeded`
* `charge.succeeded`
* `checkout.session.completed`

Your endpoint receives each payload as a separate POST request.

Use `X-Yolfi-Event-ID` as the source lifecycle identifier. One source event can intentionally fan out into multiple adapter payloads.

<Card title="Security" icon={<ArrowRight />} href="/en/webhooks/webhook-signatures" horizontal>
  Check Webhook Signatures for verification information.
</Card>

Field Mapping [#field-mapping]

Adapters put values into provider-native fields whenever there is a direct match. Provider-specific metadata is used only for Yolfi or blockchain fields that the provider does not have a native field for, such as:

* Yolfi invoice, paylink, organization, and subscription IDs
* blockchain network and chain ID
* token contract address and symbol
* merchant metadata

Adapter payloads are compatibility payloads, not canonical provider-owned objects.

Setup [#setup]

1. Open organization settings in the Yolfi dashboard.
2. Set your webhook URL.
3. Select an adapter.
4. Save changes.

What should not change:

* Your fulfillment/subscription business logic.
* Your provider webhook verification path for real provider requests.
* Your data model, provider enums, checkout URLs, or plan configuration.


# Lemon Squeezy Adapter (/zh-CN/adapters/lemon-squeezy)



Overview [#overview]

The Lemon Squeezy adapter sends a JSON:API-style body with `meta.event_name`, `meta.custom_data`, `data.type`, `data.id`, and `data.attributes`.

Yolfi webhooks are still authenticated with `X-Yolfi-Signature`. The adapter does not emulate Lemon Squeezy signatures.

Event Mapping [#event-mapping]

| Yolfi event                   | Lemon Squeezy-compatible events                       |
| :---------------------------- | :---------------------------------------------------- |
| `invoice.created`             | `order_created` with pending status                   |
| `payment.confirmed` one-time  | `order_created` with paid status                      |
| `payment.confirmed` recurring | `subscription_payment_success`                        |
| `invoice.overdue`             | `order_created` with expired status                   |
| `subscription.overdue`        | `subscription_payment_failed`, `subscription_expired` |
| `subscription.cancelled`      | `subscription_cancelled`                              |

Field Mapping [#field-mapping]

| Yolfi field                  | Lemon Squeezy field                                                 |
| :--------------------------- | :------------------------------------------------------------------ |
| `amountUsd`                  | `subtotal`, `total`, `subtotal_usd`, `total_usd` in cents           |
| `symbol`                     | `currency`; stablecoins map to `USD`                                |
| `customer.email`             | `user_email`                                                        |
| `customer.name`              | `user_name`                                                         |
| `invoiceId`                  | `data.id`, `identifier`, `order_number`, or subscription invoice ID |
| `subscriptionId`             | `subscription_id` or subscription `data.id`                         |
| `customer.clientReferenceId` | `meta.custom_data.client_reference_id`                              |
| `metadata`                   | `meta.custom_data`                                                  |

Yolfi-specific blockchain fields are stored in `meta.custom_data`: `chain_id`, `network`, `token`, and `symbol`.

Example: order_created [#example-order_created]

```json
{
  "meta": {
    "event_name": "order_created",
    "custom_data": {
      "yolfi_invoice_id": "invoice-001",
      "yolfi_paylink_id": "paylink-001",
      "yolfi_org_id": "org-001",
      "client_reference_id": "merchant-customer-001",
      "chain_id": 42161,
      "network": "ARB",
      "token": "0xaf88",
      "symbol": "USDC"
    }
  },
  "data": {
    "id": "invoice-001",
    "type": "orders",
    "attributes": {
      "identifier": "invoice-001",
      "order_number": "invoice-001",
      "user_name": "Customer One",
      "user_email": "customer@example.com",
      "currency": "USD",
      "subtotal": 1234,
      "tax": 0,
      "total": 1234,
      "status": "paid",
      "status_formatted": "Paid"
    }
  }
}
```

Example: subscription_payment_success [#example-subscription_payment_success]

```json
{
  "meta": {
    "event_name": "subscription_payment_success",
    "custom_data": {
      "yolfi_subscription_id": "subscription-001",
      "yolfi_invoice_id": "invoice-001"
    }
  },
  "data": {
    "id": "invoice-001",
    "type": "subscription-invoices",
    "attributes": {
      "subscription_id": "subscription-001",
      "billing_reason": "renewal",
      "currency": "USD",
      "status": "paid",
      "subtotal": 1234,
      "tax": 0,
      "total": 1234
    }
  }
}
```


# Stripe Adapter (/zh-CN/adapters/stripe)



Overview [#overview]

The Stripe adapter sends Stripe-style event envelopes with `object: "event"` and the provider object under `data.object`.

Yolfi webhooks are still authenticated with `X-Yolfi-Signature`. The adapter does not send or emulate `Stripe-Signature`.

Use this adapter as a compatibility bridge into existing Stripe-style handlers. Keep your business logic unchanged and only add Yolfi signature routing.

If a Stripe branch calls the Stripe API with object IDs from the webhook, keep that branch unchanged. Adapter IDs are compatibility IDs, so use a separate Yolfi endpoint or an existing body-driven handler instead of refactoring the Stripe branch.

Event Mapping [#event-mapping]

| Yolfi event                   | Stripe-compatible events                                                                    |
| :---------------------------- | :------------------------------------------------------------------------------------------ |
| `invoice.created`             | `checkout.session.created`                                                                  |
| `payment.confirmed` one-time  | `payment_intent.succeeded`, `charge.succeeded`, `checkout.session.completed`                |
| `payment.confirmed` recurring | `payment_intent.succeeded`, `charge.succeeded`, `invoice.paid`, `invoice.payment_succeeded` |
| `invoice.overdue`             | `checkout.session.expired`                                                                  |
| `subscription.overdue`        | `invoice.payment_failed`, `customer.subscription.updated`                                   |
| `subscription.cancelled`      | `customer.subscription.deleted`                                                             |

For recurring success, Stripe can surface both `invoice.paid` and `invoice.payment_succeeded`. If your business logic listens to both, deduplicate by invoice/event key to avoid double provisioning.

Field Mapping [#field-mapping]

| Yolfi field                  | Stripe field                                                                           |
| :--------------------------- | :------------------------------------------------------------------------------------- |
| `amountUsd`                  | primary source for `amount`, `amount_received`, `amount_total`, `amount_paid` in cents |
| `amount`                     | fallback source when `amountUsd` is missing                                            |
| `symbol`                     | `currency`; stablecoins map to `usd`                                                   |
| `customer.email`             | `customer_email`, `receipt_email`, `billing_details.email`                             |
| `customer.name`              | `customer_details.name`, `billing_details.name`                                        |
| `customer.clientReferenceId` | primary source for `client_reference_id`                                               |
| `customerId`                 | fallback source for `client_reference_id` and `customer`                               |
| `paylinkId`                  | `payment_link` and `metadata.yolfi_paylink_id`                                         |
| `invoiceId`                  | generated Stripe-style object IDs and `metadata.yolfi_invoice_id`                      |
| `subscriptionId`             | generated subscription ID and `metadata.yolfi_subscription_id`                         |
| `metadata`                   | Stripe `metadata` for primitive values                                                 |

Yolfi-specific blockchain fields are stored in Stripe `metadata`: `chain_id`, `network`, `token`, and `symbol`. Adapter metadata also includes Yolfi lifecycle IDs such as `yolfi_event_id`, `yolfi_invoice_id`, `yolfi_paylink_id`, and `yolfi_org_id`.

Stripe-style IDs in adapter payloads are generated compatibility IDs and may not be resolvable via Stripe API lookups.

Example: payment_intent.succeeded [#example-payment_intentsucceeded]

```json
{
  "id": "evt_evt_yolfi_001_payment_intent_succeeded",
  "object": "event",
  "api_version": "2025-04-30.basil",
  "created": 1710000000,
  "livemode": false,
  "pending_webhooks": 1,
  "request": {
    "id": null,
    "idempotency_key": null
  },
  "type": "payment_intent.succeeded",
  "data": {
    "object": {
      "id": "pi_yolfi_invoice_001",
      "object": "payment_intent",
      "amount": 1234,
      "amount_received": 1234,
      "currency": "usd",
      "customer": "cus_yolfi_customer_001",
      "latest_charge": "ch_yolfi_invoice_001",
      "receipt_email": "customer@example.com",
      "status": "succeeded",
      "metadata": {
        "yolfi_invoice_id": "invoice-001",
        "yolfi_paylink_id": "paylink-001",
        "yolfi_org_id": "org-001",
        "chain_id": "42161",
        "network": "ARB",
        "token": "0xaf88",
        "symbol": "USDC"
      }
    }
  }
}
```

Example: checkout.session.completed [#example-checkoutsessioncompleted]

```json
{
  "id": "evt_evt_yolfi_001_checkout_session_completed",
  "object": "event",
  "api_version": "2025-04-30.basil",
  "created": 1710000000,
  "livemode": false,
  "pending_webhooks": 1,
  "request": {
    "id": null,
    "idempotency_key": null
  },
  "type": "checkout.session.completed",
  "data": {
    "object": {
      "id": "cs_yolfi_invoice_001",
      "object": "checkout.session",
      "amount_subtotal": 1234,
      "amount_total": 1234,
      "currency": "usd",
      "customer": "cus_yolfi_customer_001",
      "customer_email": "customer@example.com",
      "client_reference_id": "merchant-customer-001",
      "created": 1710000000,
      "expires_at": 1710028800,
      "livemode": false,
      "metadata": {
        "yolfi_event_id": "evt_yolfi_001",
        "yolfi_invoice_id": "invoice-001",
        "yolfi_paylink_id": "paylink-001",
        "yolfi_org_id": "org-001",
        "chain_id": "42161",
        "network": "ARB",
        "token": "0xaf88",
        "symbol": "USDC"
      },
      "mode": "payment",
      "payment_link": "paylink-001",
      "payment_intent": "pi_yolfi_invoice_001",
      "payment_status": "paid",
      "status": "complete",
      "subscription": null,
      "url": null,
      "customer_details": {
        "email": "customer@example.com",
        "name": "Customer One",
        "phone": null
      }
    }
  }
}
```


# Language Support for Payment Links (/zh-CN/paylinks/language)



import { Globe } from 'lucide-react';

Overview [#overview]

Payment links automatically support 8 languages. Your customers see the payment page in their browser's preferred language by default.

You can also force a specific language by adding a language code prefix to the URL. This is useful when sharing payment links with customers who prefer a specific language.

How to Specify Language [#how-to-specify-language]

Add the two-letter language code before the payment link ID in the URL:

```
https://pay.yolfi.com/{language}/{paylink-id}
```

**Example** - to show a payment page in Spanish:

```
https://pay.yolfi.com/es/9007724b-9735-4929-a066-7e7e273a2e6d
```

Supported Languages [#supported-languages]

| Code  | Language |
| ----- | -------- |
| en    | English  |
| es    | Español  |
| de    | Deutsch  |
| fr    | Français |
| zh-CN | 简体中文     |
| ru    | Русский  |
| hi    | हिन्दी   |
| tr    | Türkçe   |

Quick Reference [#quick-reference]

<Card icon={<Globe />} title="Tip">
  When sharing payment links, consider adding the customer's preferred language code to provide a better experience. Simply prepend the language code to the URL.
</Card>


# Webhooks (/zh-CN/webhooks)



import { Rocket, Shield, Bell, Repeat, CreditCard, CheckCircle, XCircle, Clock, Bot } from 'lucide-react';

Overview [#overview]

Webhooks enable your application to receive real-time notifications whenever payment events occur in your account. Instead of polling our API for payment status, webhooks push events directly to your server the moment something happens.

<Callout type="info">
  Webhooks are the most reliable way to track payment events. Use them to automatically update your system, trigger fulfillment, or send confirmation emails.
</Callout>

<Card title="Add webhooks with an AI assistant" icon={<Bot />} href="/en/ai-integrations" horizontal cta="Open AI Integrations" arrow>
  Use the Yolfi webhook integration skill to help an AI coding assistant wire this into your backend safely.
</Card>

***

How Webhooks Work [#how-webhooks-work]

When a payment-related event occurs in our system, we send an HTTP POST request to your configured webhook URL. Your server should respond with a `200 OK` status to acknowledge receipt.

Event Types [#event-types]

Invoice Events [#invoice-events]

| Event             | Description                                     |
| :---------------- | :---------------------------------------------- |
| `invoice.created` | Payment initiated, invoice created              |
| `invoice.overdue` | Invoice payment deadline passed without payment |

Payment Events [#payment-events]

| Event               | Description                                            |
| :------------------ | :----------------------------------------------------- |
| `payment.confirmed` | Payment confirmed with enough confirmations and amount |

Subscription Events [#subscription-events]

| Event                    | Description                                          |
| :----------------------- | :--------------------------------------------------- |
| `subscription.overdue`   | Subscription renewal deadline passed without payment |
| `subscription.cancelled` | Subscription was cancelled                           |

<Callout type="info">
  Use `payment.confirmed` as your primary fulfillment trigger. This ensures payment is fully confirmed before you deliver service.
</Callout>

***

Quick Setup [#quick-setup]

<Steps>
  <Step title="Configure your webhook URL">
    Set your webhook URL in your organization settings via the API or dashboard.
  </Step>

  <Step title="Verify signatures">
    Use your API key to verify incoming webhook requests are authentic.
  </Step>

  <Step title="Handle events">
    Process the webhook payload and respond with 200 OK quickly.
  </Step>
</Steps>

***

Retry Policy [#retry-policy]

If your server returns a non-2xx status code or times out, we will retry the webhook:

1. **Immediate retry** - First attempt after failure
2. **Second attempt** - 2 minutes after first retry
3. **Final attempt** - 2 minutes after second retry

Duplicate deliveries are expected during retries. Your handler should be idempotent and deduplicate by `X-Yolfi-Event-ID`.

<Callout type="warning">
  After 3 failed attempts, the webhook is marked as failed. Ensure your endpoint is reliable and returns 200 OK quickly.
</Callout>

***

Deadlines [#deadlines]

| Scenario                                   | Deadline |
| :----------------------------------------- | :------- |
| **First payment** (one-time or recurring)  | 1 hour   |
| **Recurring cycles** (after first payment) | 24 hours |

After an invoice deadline, `invoice.overdue` is sent for that invoice. For recurring subscriptions, do not treat `invoice.overdue` as a subscription cancellation by itself; use `subscription.overdue` or `subscription.cancelled` for subscription access changes.

***

Consistent Event Model [#consistent-event-model]

All webhook events follow the same structure and include:

* **Identifier** - `id`, `invoiceId`, `orgId`
* **Amount** - `amount` (final merchant receivable, formatted), `amountUsd` (USD equivalent)
* **Pricing context** - `currency` (source currency), `rate` (payment-token quote)
* **Type** - `paymentType` (`ONE_TIME` or `RECURRING`)
* **Status** - Current state of the invoice/payment
* **Customer** - Customer info for fulfilling orders

***

Related Resources [#related-resources]

<Cards>
  <Card title="Webhook Details" description="Detailed information about each event type and payload structure" icon={<Bell />} href="/en/webhooks/webhook-details" />

  <Card title="Webhook Signatures" description="Learn how to verify webhook authenticity" icon={<Shield />} href="/en/webhooks/webhook-signatures" />

  <Card title="Adapters" description="Send webhook payloads in Stripe or Lemon Squeezy compatible formats" icon={<CreditCard />} href="/en/adapters" />

  <Card title="AI Integrations" description="Use an AI coding assistant to install webhook handling" icon={<Bot />} href="/en/ai-integrations" />
</Cards>


# Webhook Details (/zh-CN/webhooks/webhook-details)



Webhook Event Types [#webhook-event-types]

Each webhook event contains a standardized payload with information about what happened. This guide provides detailed information about each event type.

***

Common Payload Structure [#common-payload-structure]

All webhook events follow the same structure:

```json
{
  "id": "evt_abc123",
  "type": "invoice.created",
  "created": 1699900000,
  "livemode": true,
  "data": { ... }
}
```

| Field      | Type    | Description                                     |
| :--------- | :------ | :---------------------------------------------- |
| `id`       | string  | Unique event identifier (format: `evt_` + UUID) |
| `type`     | string  | Event type (e.g., `"invoice.created"`)          |
| `created`  | integer | Unix timestamp when the event was created       |
| `livemode` | boolean | `true` in production, `false` in sandbox        |
| `data`     | object  | The affected object (invoice data directly)     |

***

Invoice Events [#invoice-events]

Invoice Created [#invoice-created]

Triggered when payment is initiated (user clicks pay button).

```json
{
  "id": "evt_abc123",
  "type": "invoice.created",
  "created": 1699900000,
  "livemode": true,
  "data": {
    "paylinkId": "120e3400-e12b-04d4-a716-446417440031",
    "invoiceId": "550e8400-e29b-41d4-a716-446655440000",
    "orgId": "org_def456",
    "chainId": 42161,
    "token": "0xaf88...5831",
    "symbol": "USDC",
    "network": "ARB",
    "paymentType": "ONE_TIME",
    "amount": "1.00",
    "amountUsd": "1.00",
    "currency": "USD",
    "rate": "1.00",
    "status": "pending",
    "expiresAt": "2024-11-15T14:00:00.000Z",
    "createdAt": "2024-11-14T14:00:00.000Z",
    "updatedAt": "2024-11-14T14:00:00.000Z",
    "metadata": {},
    "customer": {
      "email": "customer@example.com",
      "clientReferenceId": "cust_123",
      "name": "John Doe"
    }
  }
}
```

Invoice Overdue [#invoice-overdue]

Triggered when payment deadline passes without payment.

This event is scoped to a single invoice/payment attempt. It does not mean an existing recurring subscription should be cancelled or access should be revoked. For subscription access changes, handle `subscription.overdue` and `subscription.cancelled`.

```json
{
  "id": "evt_abc123",
  "object": "event",
  "type": "invoice.overdue",
  "created": 1699900000,
  "livemode": true,
  "data": {
    "paylinkId": "120e3400-e12b-04d4-a716-446417440031",
    "invoiceId": "550e8400-e29b-41d4-a716-446655440000",
    "orgId": "org_def456",
    "chainId": 42161,
    "token": "0xaf88...5831",
    "symbol": "USDC",
    "network": "ARB",
    "paymentType": "RECURRING",
    "amount": "1.00",
    "amountUsd": "1.00",
    "currency": "USD",
    "rate": "1.00",
    "status": "expired",
    "expiresAt": "2024-11-15T14:00:00.000Z",
    "createdAt": "2024-11-14T14:00:00.000Z",
    "updatedAt": "2024-11-15T14:01:00.000Z",
    "metadata": {},
    "customer": {
      "email": "customer@example.com",
      "clientReferenceId": "cust_123",
      "name": "John Doe"
    }
  }
}
```

Subscription Cancelled [#subscription-cancelled]

Triggered when a subscription is cancelled.

```json
{
  "id": "evt_abc123",
  "object": "event",
  "type": "subscription.cancelled",
  "created": 1699900000,
  "livemode": true,
  "data": {
    "subscriptionId": "sub_abc123",
    "status": "cancelled",
    "expiresAt": "2024-11-15T14:00:00.000Z",
    "recurringInterval": "MONTH",
    "productName": "Premium Plan",
    "orgId": "org_def456",
    "organizationName": "Example Store",
    "customerId": "cust_123",
    "paylinkId": "120e3400-e12b-04d4-a716-446417440031",
    "invoiceId": "550e8400-e29b-41d4-a716-446655440000",
    "chainId": 42161,
    "token": "0xaf88...5831",
    "symbol": "USDC",
    "network": "ARB",
    "paymentType": "RECURRING",
    "amount": "1.00",
    "amountUsd": "1.00",
    "currency": "USD",
    "rate": "1.00",
    "createdAt": "2024-11-14T14:00:00.000Z",
    "updatedAt": "2024-11-14T15:00:00.000Z",
    "metadata": {},
    "customer": {
      "email": "customer@example.com",
      "clientReferenceId": "cust_123",
      "name": "John Doe"
    }
  }
}
```

***

Payment Events [#payment-events]

Payment Confirmed [#payment-confirmed]

Triggered when payment has received enough blockchain confirmations to be considered final.

```json
{
  "id": "evt_abc123",
  "object": "event",
  "type": "payment.confirmed",
  "created": 1699900000,
  "livemode": true,
  "data": {
    "paylinkId": "120e3400-e12b-04d4-a716-446417440031",
    "invoiceId": "550e8400-e29b-41d4-a716-446655440000",
    "orgId": "org_def456",
    "chainId": 42161,
    "token": "0xaf88...5831",
    "symbol": "USDC",
    "network": "ARB",
    "paymentType": "RECURRING",
    "amount": "1.00",
    "amountUsd": "1.00",
    "currency": "USD",
    "rate": "1.00",
    "status": "confirmed",
    "expiresAt": "2024-11-15T14:00:00.000Z",
    "createdAt": "2024-11-14T14:00:00.000Z",
    "updatedAt": "2024-11-14T15:00:00.000Z",
    "metadata": {},
    "customer": {
      "email": "customer@example.com",
      "clientReferenceId": "cust_123",
      "name": "John Doe"
    }
  }
}
```

***

Subscription Events [#subscription-events]

Subscription Overdue [#subscription-overdue]

Triggered when a subscription expires without payment renewal.

This event is subscription-scoped. Use it when your app should mark an existing subscription as past due or inactive according to your access policy.

```json
{
  "id": "evt_abc123",
  "type": "subscription.overdue",
  "created": 1699900000,
  "livemode": true,
  "data": {
    "subscriptionId": "550e8400-e29b-41d4-a716-446655440000",
    "status": "overdue",
    "expiresAt": "2024-11-15T14:00:00.000Z",
    "recurringInterval": "MONTH",
    "productName": "Premium Plan",
    "orgId": "org_def456",
    "organizationName": "Acme Corp",
    "customerId": "550e8400-e29b-41d4-a716-446655440099",
    "paylinkId": "120e3400-e12b-04d4-a716-446417440031",
    "invoiceId": "550e8400-e29b-41d4-a716-446655440001",
    "chainId": 42161,
    "token": "0xaf88...5831",
    "symbol": "USDC",
    "network": "ARB",
    "paymentType": "RECURRING",
    "amount": "1.00",
    "amountUsd": "1.00",
    "createdAt": "2024-11-01T14:00:00.000Z",
    "updatedAt": "2024-11-15T14:01:00.000Z",
    "metadata": {},
    "customer": {
      "email": "customer@example.com",
      "name": "John Doe"
    }
  }
}
```

***

Handling Recurring Customers [#handling-recurring-customers]

When you receive `invoice.created` for a recurring customer:

**Important:** Use `clientReferenceId` or `email` to identify existing customers:

* If `clientReferenceId` or `email` already exists in your database → update existing customer (don't create new)
* If new → create new customer

The same customer may have multiple invoices over time - always use `clientReferenceId` or `email` to identify them.

***

Object Field Reference [#object-field-reference]

Invoice Object [#invoice-object]

| Field         | Type    | Description                                                              |
| :------------ | :------ | :----------------------------------------------------------------------- |
| `paylinkId`   | string  | Paylink identifier                                                       |
| `invoiceId`   | string  | Invoice identifier (UUID)                                                |
| `orgId`       | string  | Organization ID                                                          |
| `chainId`     | integer | Blockchain chain ID                                                      |
| `token`       | string  | Token contract address                                                   |
| `symbol`      | string  | Token symbol (e.g., "USDC")                                              |
| `network`     | string  | Network name (e.g., "ARB")                                               |
| `paymentType` | string  | `"ONE_TIME"` or `"RECURRING"`                                            |
| `amount`      | string  | Final merchant receivable in the payment token, formatted (e.g., "1.00") |
| `amountUsd`   | string  | USD equivalent of the final merchant receivable (e.g., "1.00")           |
| `currency`    | string  | Original paylink/source currency (e.g., "USD", "EUR", "CNY")             |
| `rate`        | string  | FX quote used for pricing: one payment token in the source currency      |
| `status`      | string  | Current invoice status                                                   |
| `expiresAt`   | string  | Expiration timestamp (ISO 8601)                                          |
| `createdAt`   | string  | Creation timestamp (ISO 8601)                                            |
| `updatedAt`   | string  | Last update timestamp (ISO 8601)                                         |
| `customerId`  | string  | Customer identifier (UUID, optional)                                     |
| `metadata`    | object  | Custom metadata                                                          |
| `customer`    | object  | Customer information                                                     |

Payment Object [#payment-object]

| Field         | Type          | Description                                                              |
| :------------ | :------------ | :----------------------------------------------------------------------- |
| `paylinkId`   | string        | Paylink identifier                                                       |
| `invoiceId`   | string        | Associated invoice ID                                                    |
| `orgId`       | string        | Organization ID                                                          |
| `chainId`     | integer       | Blockchain chain ID                                                      |
| `token`       | string        | Token contract address                                                   |
| `symbol`      | string        | Token symbol (e.g., "USDC")                                              |
| `network`     | string        | Network name (e.g., "ARB")                                               |
| `paymentType` | string        | `"ONE_TIME"` or `"RECURRING"`                                            |
| `amount`      | string        | Final merchant receivable in the payment token, formatted (e.g., "1.00") |
| `amountUsd`   | string        | USD equivalent of the final merchant receivable (e.g., "1.00")           |
| `currency`    | string        | Original paylink/source currency (e.g., "USD", "EUR", "CNY")             |
| `rate`        | string        | FX quote used for pricing: one payment token in the source currency      |
| `status`      | `"confirmed"` | Payment status                                                           |
| `expiresAt`   | string        | Expiration timestamp                                                     |
| `createdAt`   | string        | Creation timestamp                                                       |
| `updatedAt`   | string        | Last update timestamp                                                    |
| `customerId`  | string        | Customer identifier (UUID, optional)                                     |
| `metadata`    | object        | Custom metadata                                                          |
| `customer`    | object        | Customer information                                                     |

Customer Object [#customer-object]

| Field               | Type   | Description                        |
| :------------------ | :----- | :--------------------------------- |
| `email`             | string | Customer email address             |
| `clientReferenceId` | string | Your internal customer reference   |
| `name`              | string | Customer name (if provided)        |
| `phone`             | string | Customer phone (if provided)       |
| `dateOfBirth`       | string | Customer dateOfBirth (if provided) |
| `address`           | string | Customer address (if provided)     |

Subscription Object [#subscription-object]

| Field               | Type    | Description                                                              |
| :------------------ | :------ | :----------------------------------------------------------------------- |
| `subscriptionId`    | string  | Subscription identifier (UUID)                                           |
| `status`            | string  | Subscription status (e.g., "overdue")                                    |
| `expiresAt`         | string  | Expiration timestamp (ISO 8601)                                          |
| `recurringInterval` | string  | Recurring interval (e.g., "MONTH")                                       |
| `productName`       | string  | Product/service name                                                     |
| `orgId`             | string  | Organization ID                                                          |
| `organizationName`  | string  | Organization name                                                        |
| `customerId`        | string  | Customer identifier                                                      |
| `paylinkId`         | string  | Paylink identifier                                                       |
| `invoiceId`         | string  | Associated invoice ID                                                    |
| `chainId`           | integer | Blockchain chain ID                                                      |
| `token`             | string  | Token contract address                                                   |
| `symbol`            | string  | Token symbol (e.g., "USDC")                                              |
| `network`           | string  | Network name (e.g., "ARB")                                               |
| `paymentType`       | string  | `"ONE_TIME"` or `"RECURRING"`                                            |
| `amount`            | string  | Final merchant receivable in the payment token, formatted (e.g., "1.00") |
| `amountUsd`         | string  | USD equivalent of the final merchant receivable (e.g., "1.00")           |
| `currency`          | string  | Original paylink/source currency (e.g., "USD", "EUR", "CNY")             |
| `rate`              | string  | FX quote used for pricing: one payment token in the source currency      |
| `createdAt`         | string  | Subscription creation timestamp                                          |
| `updatedAt`         | string  | Last update timestamp                                                    |
| `metadata`          | object  | Custom metadata                                                          |
| `customer`          | object  | Customer information                                                     |


# Webhook Signatures (/zh-CN/webhooks/webhook-signatures)



import { Shield, Lock, AlertTriangle } from 'lucide-react';

Overview [#overview]

Every webhook request includes a signature in the header that you can verify to ensure the request genuinely came from Yolfi and wasn't tampered with during transit.

<Callout type="warning">
  Always verify webhook signatures before processing any webhook event. Failing to do so could allow attackers to send fake payment notifications to your system.
</Callout>

***

Why Verify Signatures? [#why-verify-signatures]

Signature verification provides two key security guarantees:

1. **Authenticity** - Confirms the webhook was sent by Yolfi, not an imposter
2. **Integrity** - Ensures the payload wasn't modified in transit
   Replay/deduplication is an application concern: use `X-Yolfi-Event-ID` with your idempotency pattern to avoid duplicate side effects.

***

How It Works [#how-it-works]

Signature Generation [#signature-generation]

When we send a webhook, we:

1. Create a JSON payload with the event data
2. Generate an HMAC-SHA256 signature using your API key as the secret
3. Encode the signature in Base64
4. Send it in the request headers

Headers [#headers]

Each webhook request includes these headers:

| Header              | Description                          |
| :------------------ | :----------------------------------- |
| `Content-Type`      | Always `application/json`            |
| `X-Yolfi-Signature` | Base64-encoded HMAC-SHA256 signature |
| `X-Yolfi-Event-ID`  | Unique event identifier              |

Verification Process [#verification-process]

To verify a webhook signature:

<Steps>
  <Step title="Get the signature">
    Extract the `X-Yolfi-Signature` header from the request.
  </Step>

  <Step title="Get your API key">
    Retrieve your API key from your organization settings.
  </Step>

  <Step title="Compute expected signature">
    Calculate HMAC-SHA256 of the raw request body using your API key.
  </Step>

  <Step title="Compare signatures">
    Use constant-time comparison to compare your computed signature with the one in the header.
  </Step>
</Steps>

***

Code Examples [#code-examples]

<Tabs items={["Node.js", "Python", "Go"]}>
  <Tab value="Node.js">
    ```javascript
    const crypto = require('crypto');

    function verifyWebhookSignature(payload, signature, apiKey) {
      if (!signature || !apiKey) {
        return false;
      }

      const expected = crypto
        .createHmac('sha256', apiKey)
        .update(payload, 'utf8')
        .digest('base64');

      try {
        return crypto.timingSafeEqual(
          Buffer.from(signature, 'base64'),
          Buffer.from(expected, 'base64')
        );
      } catch (e) {
        return false;
      }
    }

    // Express.js example
    app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
      const signature = req.headers['x-yolfi-signature'];
      const payload = req.body.toString(); // Keep raw body as string
      const apiKey = process.env.YOLFI_API_KEY;

      const isValid = verifyWebhookSignature(payload, signature, apiKey);

      if (!isValid) {
        return res.status(400).send('Invalid signature');
      }

      const event = JSON.parse(payload);
      console.log('Received webhook:', event.type);

      // Process the event
      res.status(200).send('OK');
    });
    ```
  </Tab>

  <Tab value="Python">
    ```python
    import hmac
    import hashlib
    import base64
    import os

    def verify_signature(payload: str, signature: str, api_key: str) -> bool:
        if not signature or not api_key:
            return False

        expected = hmac.new(
            api_key.encode('utf-8'),
            payload.encode('utf-8'),
            hashlib.sha256
        ).digest()

        return hmac.compare_digest(
            base64.b64decode(signature),
            expected
        )

    # Flask example
    @app.route('/webhook', methods=['POST'])
    def webhook():
        signature = request.headers.get('X-Yolfi-Signature')
        payload = request.get_data(as_text=True)
        api_key = os.environ.get('YOLFI_API_KEY')

        if not verify_signature(payload, signature, api_key):
            return 'Invalid signature', 400

        event = request.get_json()
        print(f"Received webhook: {event['type']}")
        return 'OK', 200
    ```
  </Tab>

  <Tab value="Go">
    ```go
    import (
        "crypto/hmac"
        "crypto/sha256"
        "crypto/subtle"
        "encoding/base64"
        "os"
    )

    func verifySignature(payload string, signature string, apiKey string) bool {
        if signature == "" || apiKey == "" {
            return false
        }

        expected := hmac.New(sha256.New, []byte(apiKey))
        expected.Write([]byte(payload))

        expectedSig, _ := base64.StdEncoding.DecodeString(expected.Sum(nil))
        sig, _ := base64.StdEncoding.DecodeString(signature)

        return subtle.ConstantTimeCompare(expectedSig, sig) == 1
    }

    // Echo framework example
    e.POST("/webhook", func(c echo.Context) error {
        signature := c.Request().Header.Get("X-Yolfi-Signature")
        payload, _ := io.ReadAll(c.Request().Body)
        apiKey := os.Getenv("YOLFI_API_KEY")

        if !verifySignature(string(payload), signature, apiKey) {
            return c.String(400, "Invalid signature")
        }

        var event map[string]interface{}
        json.Unmarshal(payload, &event)
        fmt.Printf("Received webhook: %v\n", event["type"])

        return c.String(200, "OK")
    })
    ```
  </Tab>
</Tabs>

***

Security Best Practices [#security-best-practices]

<Steps>
  <Step title="Use timing-safe comparison">
    Always use constant-time comparison functions (`crypto.timingSafeEqual` in Node.js, `hmac.compare_digest` in Python) to prevent timing attacks.
  </Step>

  <Step title="Store API keys securely">
    Keep your API keys in environment variables or a secure secrets manager. Never commit them to version control.
  </Step>

  <Step title="Verify the entire payload">
    Compute the signature over the raw request body, not a parsed/re-serialized version.
  </Step>

  <Step title="Respond quickly">
    Verify the signature and return 200 OK immediately. Process the event asynchronously afterward.
  </Step>

  <Step title="Handle duplicates">
    Webhooks may be retried on failure. Design your handler to be idempotent and deduplicate using `X-Yolfi-Event-ID`.
  </Step>
</Steps>
