# Changelog

Source: https://docs.halfin.xyz/changelog
Markdown: https://docs.halfin.xyz/md/changelog

## Current Version

The public merchant API is versioned under `/v1`.

## Recent Changes

* Auth and checkout UI endpoints are hidden from public merchant documentation.
* Merchant, public data, and webhook event references are generated from the public OpenAPI surface.
* Concept pages now explain addresses, invoices, payouts, balances, idempotency, pagination, errors, and authentication.

---

# Environments

Source: https://docs.halfin.xyz/environments
Markdown: https://docs.halfin.xyz/md/environments

halfin separates test and live data at the API-key level. A test key can only see test invoices, balances, payouts, and webhook deliveries. A live key can only see live resources.

## Base URL

The current API base URL is:

```text
https://dashboard.halfin.xyz/api
```

Use `sk_test_...` keys while building and `sk_live_...` keys only from production server-side code.

## Switching Environments

Merchant API requests do not need an `environment` query parameter. The key determines the environment.

Public data endpoints can accept an environment filter when the response depends on live or test offerability:

```bash
curl -G https://dashboard.halfin.xyz/api/v1/currencies \
  --data-urlencode "environment=test"
```

## Going Live

Before switching to a live key, verify webhook signatures, idempotency keys, and payout approval rules in test mode.

---

# Get Started

Source: https://docs.halfin.xyz/get-started
Markdown: https://docs.halfin.xyz/md/get-started

import { Callout } from 'fumadocs-ui/components/callout';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
import { Step, Steps } from 'fumadocs-ui/components/steps';

## Prerequisites

<Steps>
  <Step>
    ### Create an account

    Sign up at [dashboard.halfin.xyz](https://dashboard.halfin.xyz).
  </Step>

  <Step>
    ### Generate an API key

    In the dashboard, go to **Settings -> API Keys** and create a test key.
  </Step>

  <Step>
    ### Configure webhooks

    Add a server endpoint and save the webhook secret for signature verification.
  </Step>
</Steps>

## First invoice

Create invoices from your server with an idempotency key:

<Tabs items={['cURL', 'TypeScript']}>
  <Tab value="cURL">
    ```bash
    curl -X POST https://dashboard.halfin.xyz/api/v1/invoices \
      -H "Content-Type: application/json" \
      -H "X-API-Key: sk_test_0000000000000000000000000000000000000000000000000000000000000000" \
      -d '{
        "currency": "BTC",
        "amount": "0.001",
        "description": "Order #1234",
        "idempotency_key": "order-1234"
      }'
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts
    import { createHalfin, createInvoice } from '@halfin/sdk-merchant';

    const client = createHalfin({
      apiKey: process.env.HALFIN_API_KEY!,
      baseUrl: 'https://dashboard.halfin.xyz/api',
    });

    const { data } = await createInvoice({
      client,
      body: {
        currency: 'BTC',
        amount: '0.001',
        description: 'Order #1234',
        idempotency_key: 'order-1234',
      },
    });
    ```
  </Tab>
</Tabs>

Use the invoice status, payment address details, and webhooks to decide when an order can be fulfilled.

<Callout type="warn">
  Never fulfill an order from a browser redirect or client-side signal alone. Confirm payment server-side through a verified webhook or an invoice status check.
</Callout>

## Next steps

* [Payment Flows](/guides/payment-flows) - follow the current invoice payment-link path.
* [Authentication](/guides/authentication) - API key format and handling.
* [Invoices](/concepts/invoices) - invoice fields, statuses, and payment lifecycle.
* [Webhooks](/webhooks) - events and signature verification.
* [API Reference](/api/merchant) - generated per-operation pages.

---

# Overview

Source: https://docs.halfin.xyz/
Markdown: https://docs.halfin.xyz/md

import { Cards, Card } from 'fumadocs-ui/components/card';
import {
  BookOpen,
  Code,
  Compass,
  Globe2,
  KeyRound,
  MapPin,
  Receipt,
  Route,
  Send,
  Webhook,
} from 'lucide-react';

halfin gives merchant developers API primitives for creating addresses, invoices, and payouts, plus the supporting surfaces needed to run them safely.

## Start

<Cards>
  <Card href="/get-started" icon={<Compass />} title="Get Started">
    Create a first invoice and confirm it from your server.
  </Card>

  <Card href="/guides/payment-flows" icon={<Route />} title="Payment Flows">
    Follow the current invoice payment-link path.
  </Card>

  <Card href="/environments" icon={<Globe2 />} title="Environments">
    Test and live keys, base URL, and environment isolation.
  </Card>
</Cards>

## Concepts

<Cards>
  <Card href="/concepts/authentication" icon={<KeyRound />} title="Authentication">
    API keys, permissions, and server-side key handling.
  </Card>

  <Card href="/concepts/addresses" icon={<MapPin />} title="Addresses">
    Static and invoice-scoped payment addresses.
  </Card>

  <Card href="/concepts/invoices" icon={<Receipt />} title="Invoices">
    One-time payment requests, statuses, and payment lifecycle.
  </Card>

  <Card href="/concepts/payouts" icon={<Send />} title="Payouts">
    Send funds from merchant balances.
  </Card>
</Cards>

## Guides

<Cards>
  <Card href="/guides" icon={<BookOpen />} title="Guides">
    Payment flows, authentication, static addresses, SDK usage, rate limits, and errors.
  </Card>

  <Card href="/webhooks" icon={<Webhook />} title="Webhooks">
    Event catalog, delivery semantics, and signature verification.
  </Card>
</Cards>

## API Reference

<Cards>
  <Card href="/api/merchant" icon={<Code />} title="Merchant API">
    Invoices, payouts, addresses, balances, and ledger endpoints.
  </Card>

  <Card href="/api/public" icon={<Globe2 />} title="Public API">
    Exchange rates and supported currency metadata.
  </Card>

  <Card href="/api/webhooks" icon={<Webhook />} title="Webhook Schemas">
    Generated payload schemas for every public webhook event.
  </Card>
</Cards>

---

# Addresses

Source: https://docs.halfin.xyz/concepts/addresses
Markdown: https://docs.halfin.xyz/md/concepts/addresses

## Reference

halfin uses two address patterns. Invoice addresses are created for a single payment request. Static addresses are permanent deposit addresses that auto-create invoices when funds arrive.

| Address type    | Created by           | Best for                  |
| --------------- | -------------------- | ------------------------- |
| Invoice address | `POST /v1/invoices`  | One customer payment      |
| Static address  | `POST /v1/addresses` | Deposits, account top-ups |

## Usage

Create a static address when the same payer can deposit more than once. Static addresses are not payment pages; they are reusable deposit rails that still report payment state through invoice and balance webhooks.

```bash
curl -X POST https://dashboard.halfin.xyz/api/v1/addresses \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $HALFIN_API_KEY" \
  -d '{"currency":"BTC","label":"Account top-up"}'
```

```ts
import { createAddress, createHalfin } from '@halfin/sdk-merchant';

const client = createHalfin({ apiKey: process.env.HALFIN_API_KEY });
const { data } = await createAddress({
  client,
  body: { currency: 'BTC', label: 'Account top-up' },
});
```

## Pitfalls

* Some chains require a memo or destination tag. Show it beside the address.
* Do not treat a static address deposit as paid until the invoice webhook confirms it.

## Troubleshooting

**Deposit not visible** usually means the transaction is still unconfirmed, sent on the wrong network, or missing a required memo/tag.

---

# Authentication

Source: https://docs.halfin.xyz/concepts/authentication
Markdown: https://docs.halfin.xyz/md/concepts/authentication

import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
import { Callout } from 'fumadocs-ui/components/callout';

## Reference

Merchant integrations authenticate with an API key in the `X-API-Key` header. Dashboard login, MFA, password reset, and checkout token endpoints are first-party UI flows and are not part of the public merchant API.

| Key type      | Environment | Use                 |
| ------------- | ----------- | ------------------- |
| `sk_test_...` | test        | Sandbox development |
| `sk_live_...` | live        | Production payments |

## Usage

Use the same key on every server-side request.

<Tabs items={['cURL', 'TypeScript']}>
  <Tab value="cURL">
    ```bash
    curl https://dashboard.halfin.xyz/api/v1/invoices \
      -H "X-API-Key: $HALFIN_API_KEY"
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts
    import { createHalfin, listInvoices } from '@halfin/sdk-merchant';

    const client = createHalfin({ apiKey: process.env.HALFIN_API_KEY });
    const { data } = await listInvoices({ client });
    ```
  </Tab>
</Tabs>

## Pitfalls

<Callout type="warn">
  * Do not call login or MFA endpoints from a merchant integration.
  * Do not expose live keys in frontend code.
  * Do not reuse a live key in sandbox tests.
</Callout>

## Troubleshooting

**401 unauthorized** means the key is missing, malformed, revoked, or from the wrong environment.

**403 forbidden** means the key is valid but does not have the permission required by the endpoint.

---

# Balances and Ledger

Source: https://docs.halfin.xyz/concepts/balances-ledger
Markdown: https://docs.halfin.xyz/md/concepts/balances-ledger

## Reference

Balances show current funds per currency. The ledger explains how funds moved through deposits, fees, payouts, late deposits, and reversals.

Use `balance.credited` when fulfillment or reconciliation depends on spendable merchant balance availability.

| Surface                              | Use                                 |
| ------------------------------------ | ----------------------------------- |
| `GET /v1/balances`                   | Current balance rows                |
| `GET /v1/balances/{currency}/ledger` | Historical entries for one currency |

## Usage

Read balances for an overview, then inspect a currency ledger when reconciling.

```bash
curl https://dashboard.halfin.xyz/api/v1/balances \
  -H "X-API-Key: $HALFIN_API_KEY"
```

```ts
import { createHalfin, getLedger, listBalances } from '@halfin/sdk-merchant';

const client = createHalfin({ apiKey: process.env.HALFIN_API_KEY });
const balances = await listBalances({ client });
const btcLedger = await getLedger({ client, path: { currency: 'BTC' } });
```

## Pitfalls

* Do not calculate spendable funds from webhook totals alone.
* Treat `invoice.paid` as payment confirmation; use `balance.credited` for spendable balance credit.
* Treat reversals and late deposits as reconciliation events, not normal paid invoices.

## Troubleshooting

**Balance differs from invoice totals** usually means fees, payouts, reversals, or late deposits are included in the ledger.

---

# Errors

Source: https://docs.halfin.xyz/concepts/errors
Markdown: https://docs.halfin.xyz/md/concepts/errors

import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
import { Callout } from 'fumadocs-ui/components/callout';

## Reference

Errors use a consistent envelope. Use `error.code` for programmatic handling and include `meta.request_id` when contacting support.

```json
{
  "error": {
    "code": "not_found",
    "message": "invoice not found"
  },
  "meta": {
    "request_id": "req_0000000000000001"
  }
}
```

## Usage

Handle retryable and non-retryable errors separately.

<Tabs items={['cURL', 'TypeScript']}>
  <Tab value="cURL">
    ```bash
    curl https://dashboard.halfin.xyz/api/v1/invoices/00000000-0000-0000-0000-000000000001 \
      -H "X-API-Key: $HALFIN_API_KEY"
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts
    try {
      // Call a generated SDK method here.
    } catch (error) {
      console.error(error);
    }
    ```
  </Tab>
</Tabs>

<Callout type="info">
  Always include `meta.request_id` in support tickets so on-call can find the trace immediately.
</Callout>

## Pitfalls

<Callout type="warn">
  * Retry 429 and temporary 5xx errors with backoff.
  * Do not retry validation errors without changing the request.
  * Do not show raw internal error details to customers.
</Callout>

## Troubleshooting

**401** means missing or invalid credentials.

**403** means valid credentials without the required permission.

**503 gate\_offline** means a blockchain processor is temporarily unavailable.

---

# Idempotency

Source: https://docs.halfin.xyz/concepts/idempotency
Markdown: https://docs.halfin.xyz/md/concepts/idempotency

import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
import { Callout } from 'fumadocs-ui/components/callout';

## Reference

Create operations accept an `idempotency_key`. Repeating the same request with the same key returns the original resource. Reusing the key with a different body returns an idempotency mismatch error.

## Usage

Use a stable key from your order, job, or payout identifier.

<Tabs items={['cURL', 'TypeScript']}>
  <Tab value="cURL">
    ```bash
    curl -X POST https://dashboard.halfin.xyz/api/v1/invoices \
      -H "Content-Type: application/json" \
      -H "X-API-Key: $HALFIN_API_KEY" \
      -d '{"currency":"BTC","amount":"0.01000000","idempotency_key":"order-0001"}'
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts
    import { createHalfin, createInvoice } from '@halfin/sdk-merchant';

    const client = createHalfin({ apiKey: process.env.HALFIN_API_KEY });
    await createInvoice({
      client,
      body: { currency: 'BTC', amount: '0.01000000', idempotency_key: 'order-0001' },
    });
    ```
  </Tab>
</Tabs>

## Pitfalls

<Callout type="warn">
  * Do not generate a new idempotency key for every retry.
  * Do not reuse one key across unrelated orders.
</Callout>

## Troubleshooting

**Idempotency key mismatch** means the original request body and retry body differ. Reuse the original body or start a new operation with a new key.

---

# Invoices

Source: https://docs.halfin.xyz/concepts/invoices
Markdown: https://docs.halfin.xyz/md/concepts/invoices

import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
import { Callout } from 'fumadocs-ui/components/callout';

## Reference

An invoice is a one-time payment request. It defines what the customer should pay and returns status, amount fields, and payment address details.

| Field                           | Purpose                          |
| ------------------------------- | -------------------------------- |
| `amount`                        | Crypto-denominated amount        |
| `amount_fiat` + `fiat_currency` | Fiat-denominated amount          |
| `idempotency_key`               | Duplicate protection for retries |

## Usage

Create an invoice from your server. Send the payer to the returned `checkout_url` payment link, then confirm the result from your backend with a verified webhook or a server-side invoice status check.

<Tabs items={['cURL', 'TypeScript']}>
  <Tab value="cURL">
    ```bash
    curl -X POST https://dashboard.halfin.xyz/api/v1/invoices \
      -H "Content-Type: application/json" \
      -H "X-API-Key: $HALFIN_API_KEY" \
      -d '{"currency":"BTC","amount":"0.01000000","idempotency_key":"order-0001"}'
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts
    import { createHalfin, createInvoice } from '@halfin/sdk-merchant';

    const client = createHalfin({ apiKey: process.env.HALFIN_API_KEY });
    const { data } = await createInvoice({
      client,
      body: { currency: 'BTC', amount: '0.01000000', idempotency_key: 'order-0001' },
    });
    ```
  </Tab>
</Tabs>

## Pitfalls

<Callout type="warn">
  * Use idempotency keys when retrying invoice creation.
  * Do not fulfill an order from a redirect alone. Confirm with webhooks or a server-side status check.
  * Static address deposits create invoices after funds arrive; they are not checkout sessions.
</Callout>

## Troubleshooting

**Invoice remains pending** means no sufficient on-chain payment has been confirmed yet.

**Idempotency mismatch** means the same key was retried with a different request body.

---

# Pagination

Source: https://docs.halfin.xyz/concepts/pagination
Markdown: https://docs.halfin.xyz/md/concepts/pagination

## Reference

List endpoints return a pagination object with `total`, `limit`, `offset`, and `has_more`. Merchant list endpoints use `limit` and `offset`.

| Parameter | Default | Maximum |
| --------- | ------: | ------: |
| `limit`   |      20 |     100 |
| `offset`  |       0 |       - |

## Usage

Increase `offset` by the returned `limit` until `has_more` is false.

```bash
curl -G https://dashboard.halfin.xyz/api/v1/invoices \
  -H "X-API-Key: $HALFIN_API_KEY" \
  --data-urlencode "limit=20" \
  --data-urlencode "offset=0"
```

```ts
import { createHalfin, listInvoices } from '@halfin/sdk-merchant';

const client = createHalfin({ apiKey: process.env.HALFIN_API_KEY });
const { data, meta } = await listInvoices({
  client,
  query: { limit: 20, offset: 0 },
});
```

## Pitfalls

* Do not assume a list response contains every historical row.
* Keep filters stable while paginating.

## Troubleshooting

**Missing rows between pages** usually means the filter or sort window changed while you were paginating. Restart the scan with the same filters.

---

# Payouts

Source: https://docs.halfin.xyz/concepts/payouts
Markdown: https://docs.halfin.xyz/md/concepts/payouts

import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
import { Callout } from 'fumadocs-ui/components/callout';

## Reference

A payout sends funds from your merchant balance to an external blockchain address. New payouts enter `pending_approval` and require approval before execution.

| State              | Meaning                              |
| ------------------ | ------------------------------------ |
| `pending_approval` | Created, awaiting dashboard approval |
| `processing`       | Submitted for blockchain execution   |
| `completed`        | Confirmed on-chain                   |
| `failed`           | Failed and funds were released       |

## Usage

Create payouts from trusted server-side code only.

<Tabs items={['cURL', 'TypeScript']}>
  <Tab value="cURL">
    ```bash
    curl -X POST https://dashboard.halfin.xyz/api/v1/payouts \
      -H "Content-Type: application/json" \
      -H "X-API-Key: $HALFIN_API_KEY" \
      -d '{"currency":"BTC","amount":"0.01000000","destination":"bc1qexampleaddress000000000000000000000000"}'
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts
    import { createHalfin, createPayout } from '@halfin/sdk-merchant';

    const client = createHalfin({ apiKey: process.env.HALFIN_API_KEY });
    const { data } = await createPayout({
      client,
      body: {
        currency: 'BTC',
        amount: '0.01000000',
        destination: 'bc1qexampleaddress000000000000000000000000',
      },
    });
    ```
  </Tab>
</Tabs>

## Pitfalls

<Callout type="warn">
  * Validate destination addresses before creating a payout.
  * Keep payout keys more restricted than invoice keys.
</Callout>

## Troubleshooting

**403 forbidden** usually means the API key lacks `payouts:write`.

**Payout stuck in pending approval** means it has not yet been approved in the dashboard.

---

# Authentication

Source: https://docs.halfin.xyz/guides/authentication
Markdown: https://docs.halfin.xyz/md/guides/authentication

import { Callout } from 'fumadocs-ui/components/callout';

All merchant API requests authenticate with an API key passed in the `X-API-Key` header.

## API key types

| Prefix     | Environment | Purpose                                 |
| ---------- | ----------- | --------------------------------------- |
| `sk_test_` | Test        | Development and testing - no real funds |
| `sk_live_` | Production  | Real blockchain transactions            |

Keys are 64-character hex strings prefixed with the environment: `sk_test_{64 hex chars}`.

## Header format

```
X-API-Key: sk_test_0000000000000000000000000000000000000000000000000000000000000000
```

## Environment isolation

Test and live environments are completely separate:

* **Test keys** only access test invoices, test balances, and test resources
* **Live keys** only access live invoices, live balances, and real blockchains
* You cannot mix environments - a test key will never see live data

## Using the SDK

```typescript
import { createHalfin } from '@halfin/sdk-merchant';

const client = createHalfin({
  apiKey: process.env.HALFIN_API_KEY!, // sk_test_... or sk_live_...
  baseUrl: 'https://dashboard.halfin.xyz/api',
});
```

## Best practices

<Callout type="warn">
  **Never commit API keys** to version control. Use environment variables or a secrets manager.
</Callout>

* **Rotate keys** periodically in **Settings → API Keys** on the dashboard.
* **Use test keys** in CI/CD pipelines and staging environments.
* **Restrict permissions** - create keys with only the scopes your integration needs (e.g., `invoices:write` without `addresses:write`).
* **Keep live keys server-side only** - never expose them in frontend code or client-side bundles.

---

# Error Handling

Source: https://docs.halfin.xyz/guides/error-handling
Markdown: https://docs.halfin.xyz/md/guides/error-handling

import { Callout } from 'fumadocs-ui/components/callout';

All API errors follow a consistent envelope format. Use the `code` field for programmatic handling and `message` for user-facing display.

## Error envelope

```json
{
  "error": {
    "code": "not_found",
    "message": "invoice not found",
    "details": []
  },
  "meta": {
    "request_id": "req_abc123"
  }
}
```

<Callout type="info">
  Always include `meta.request_id` in support tickets for faster debugging.
</Callout>

## Error codes

| HTTP | Code               | Description                                                |
| ---- | ------------------ | ---------------------------------------------------------- |
| 400  | `validation_error` | Request body or parameters failed validation               |
| 400  | `invalid_state`    | Operation not allowed in current resource state            |
| 401  | `unauthorized`     | Missing or invalid API key / JWT                           |
| 403  | `forbidden`        | Valid credentials but insufficient permissions             |
| 404  | `not_found`        | Resource does not exist                                    |
| 409  | `conflict`         | Idempotency key reused with different parameters           |
| 429  | `rate_limited`     | Too many requests - see [Rate Limits](/guides/rate-limits) |
| 500  | `internal_error`   | Server error - retry with backoff                          |
| 503  | `gate_offline`     | Blockchain processor temporarily unavailable               |

## Handling errors

```typescript
import { createHalfin, createInvoice } from '@halfin/sdk-merchant';

const client = createHalfin({ apiKey: process.env.HALFIN_API_KEY });

try {
  const invoice = await createInvoice({
    client,
    body: {
      currency: 'BTC',
      amount: '0.001',
    },
  });
} catch (err) {
  if (err.status === 429) {
    // Back off and retry
    await sleep(parseInt(err.headers['retry-after'] || '60', 10) * 1000);
    return retry();
  }

  if (err.body?.error?.code === 'gate_offline') {
    // Try a different currency or show a maintenance message
  }

  console.error(`halfin error: ${err.body?.error?.message} (${err.body?.meta?.request_id})`);
}
```

<Callout type="warn">
  Retry only `429` and `5xx`. Validation errors (`400 validation_error`) and `409 conflict` will not change without modifying the request - retrying them just wastes quota.
</Callout>

## Idempotency keys

Prevent duplicate invoices by passing an `idempotency_key`:

```typescript
const invoice = await createInvoice({
  client,
  body: {
    currency: 'BTC',
    amount: '0.001',
    idempotency_key: 'order-1234-attempt-1',
  },
});
```

* If you retry with the **same key and same parameters**, you get the original response.
* If you retry with the **same key but different parameters**, you get a `409 conflict` error.
* Keys are scoped to your merchant account and environment.

---

# Guides

Source: https://docs.halfin.xyz/guides
Markdown: https://docs.halfin.xyz/md/guides

import { Cards, Card } from 'fumadocs-ui/components/card';
import { AlertTriangle, Code, KeyRound, MapPin, RadioTower, Receipt, Route } from 'lucide-react';

Use these guides with the generated [API Reference](/api/merchant). They cover the public merchant API primitives and supporting operational concerns.

<Cards>
  <Card href="/guides/payment-flows" icon={<Receipt />} title="Payment Flows">
    Invoice payment links, status checks, balances, and payouts.
  </Card>

  <Card href="/guides/authentication" icon={<KeyRound />} title="Authentication">
    API key types, header format, and safe storage.
  </Card>

  <Card href="/guides/static-addresses" icon={<MapPin />} title="Static Addresses">
    Permanent deposit addresses for recurring deposits.
  </Card>

  <Card href="/guides/rate-limits" icon={<RadioTower />} title="Rate Limits">
    Handling 429 responses and backoff.
  </Card>

  <Card href="/guides/error-handling" icon={<AlertTriangle />} title="Error Handling">
    Error envelope, request IDs, and retry boundaries.
  </Card>

  <Card href="/guides/sdks/typescript" icon={<Code />} title="TypeScript SDK">
    Install and use the official merchant SDK.
  </Card>

  <Card href="/environments" icon={<Route />} title="Environments">
    Test and live keys, isolation, and going live.
  </Card>
</Cards>

---

# Payment Flows

Source: https://docs.halfin.xyz/guides/payment-flows
Markdown: https://docs.halfin.xyz/md/guides/payment-flows

Use this page to follow the current first integration path. It is a routing guide, not a replacement for the API reference.

## Current first path

Start with server-created invoices and the returned payment link:

1. Create an API key in the dashboard.
2. Create an invoice from your server with an idempotency key.
3. Send the payer to the invoice `checkout_url` payment link.
4. Confirm fulfillment from a verified webhook or a server-side invoice status check.
5. Reconcile credited funds with balances and the ledger.
6. Create payouts from trusted server-side code when funds are ready to move.

This keeps API keys server-side and keeps fulfillment decisions on your backend.

## Where each surface fits

| Surface             | What it does                                      | Merchant action                                                 | Where to continue                                           |
| ------------------- | ------------------------------------------------- | --------------------------------------------------------------- | ----------------------------------------------------------- |
| API keys            | Authenticate server-side merchant API calls       | Create a test key, keep live keys on your backend               | [Authentication](/guides/authentication)                    |
| Invoices            | Create one payment request and get a payment link | Send the payer to `checkout_url`, then check status server-side | [Get Started](/get-started), [Invoices](/concepts/invoices) |
| Balances and ledger | Show spendable funds and audit history            | Reconcile credits, fees, payouts, reversals, and late deposits  | [Balances and Ledger](/concepts/balances-ledger)            |
| Payouts             | Move funds from merchant balances                 | Create payout requests from trusted server-side code            | [Payouts](/concepts/payouts)                                |
| Static addresses    | Reusable deposit rails that auto-create invoices  | Use for deposits, account top-ups, or repeated deposits         | [Static Addresses](/guides/static-addresses)                |

## Static addresses are deposit rails

Static addresses auto-create invoices after deposits arrive, so use invoice status checks and balance events before crediting a user account. They are useful for repeat deposits, not for replacing the invoice payment-link path for a specific order.

## Main integration path

* [Authentication](/guides/authentication) - create and protect API keys.
* [Get Started](/get-started) - create the first invoice.
* [Invoices](/concepts/invoices) - understand invoice fields, payment links, and statuses.
* [Balances and Ledger](/concepts/balances-ledger) - reconcile spendable funds.
* [Payouts](/concepts/payouts) - send funds from merchant balances.
* [Addresses](/concepts/addresses) - understand invoice addresses and static deposit rails.
* [Webhooks](/webhooks) - confirm payments and balance credits.
* [Merchant API](/api/merchant) - generated endpoint reference.

---

# Rate Limits

Source: https://docs.halfin.xyz/guides/rate-limits
Markdown: https://docs.halfin.xyz/md/guides/rate-limits

halfin enforces rate limits to ensure fair usage and platform stability.

## Limits

| Scope                  | Limit            | Applies to                     |
| ---------------------- | ---------------- | ------------------------------ |
| Per-merchant (API key) | 300 requests/min | All `/v1/*` merchant endpoints |
| Per-IP (public)        | 120 requests/min | `/v1/rates`, `/v1/currencies`  |

Limits reset on a rolling 60-second window.

## Response Headers

Rate-limited responses include:

```
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 12
```

```json
{
  "error": {
    "code": "rate_limited",
    "message": "rate limit exceeded, retry after 12 seconds"
  },
  "meta": { "request_id": "req_abc123" }
}
```

## Handling 429 Responses

```typescript
import { createHalfin, createInvoice } from '@halfin/sdk-merchant';

const client = createHalfin({ apiKey: process.env.HALFIN_API_KEY });

async function withRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (err: any) {
      if (err.status !== 429 || attempt === maxRetries) throw err;

      const retryAfter = parseInt(err.headers?.['retry-after'] || '60', 10);
      await new Promise((r) => setTimeout(r, retryAfter * 1000));
    }
  }
  throw new Error('unreachable');
}

// Usage
const invoice = await withRetry(() =>
  createInvoice({
    client,
    body: { currency: 'BTC', amount: '0.001' },
  }),
);
```

## Best Practices

* **Use webhooks** instead of polling for payment status where possible
* **Cache** `/v1/rates` and `/v1/currencies` responses (they change infrequently)
* **Batch operations** server-side rather than making one API call per user action
* **Implement exponential backoff** using the `Retry-After` header value

---

# Static Addresses

Source: https://docs.halfin.xyz/guides/static-addresses
Markdown: https://docs.halfin.xyz/md/guides/static-addresses

Static addresses are permanent blockchain addresses tied to your merchant account. Any deposit to a static address automatically creates an invoice.

Use static addresses for deposits or top-ups, not as a replacement for invoice payment links. If a customer is paying a specific order, start with [Payment Flows](/guides/payment-flows) and invoices.

## Creating a Static Address

```bash
curl -X POST https://dashboard.halfin.xyz/api/v1/addresses \
  -H "Content-Type: application/json" \
  -H "X-API-Key: sk_test_0000000000000000000000000000000000000000000000000000000000000000" \
  -d '{
    "currency": "BTC",
    "label": "Account top-up"
  }'
```

Response:

```json
{
  "data": {
    "id": "00000000-0000-0000-0000-000000000002",
    "currency": "BTC",
    "address": "bc1qstaticexampleaddress",
    "address_tag": null,
    "label": "Account top-up",
    "total_received": "0",
    "invoice_count": 0,
    "created_at": "2026-01-01T00:00:00Z"
  },
  "meta": { "request_id": "req_def456" }
}
```

## With the SDK

```typescript
import { createAddress, createHalfin } from '@halfin/sdk-merchant';

const client = createHalfin({
  apiKey: process.env.HALFIN_API_KEY!,
  baseUrl: 'https://dashboard.halfin.xyz/api',
});

const { data } = await createAddress({
  client,
  body: {
    currency: 'BTC',
    label: 'Account top-up',
  },
});

console.log(data.address);
// → "bc1qstaticexampleaddress"
```

## How It Works

1. You create a static address for a currency
2. Store the address on your backend for the payer or account top-up flow
3. When a deposit arrives, halfin auto-creates an invoice with `source: "static_address"`
4. You receive `invoice.confirming` and `invoice.paid` webhooks as usual

## Listing Invoices for an Address

```bash
curl https://dashboard.halfin.xyz/api/v1/addresses/00000000-0000-0000-0000-000000000002/invoices \
  -H "X-API-Key: sk_test_0000000000000000000000000000000000000000000000000000000000000000"
```

## Use Cases

* **Account top-ups** -- assign one address per user for balance deposits
* **Treasury deposits** -- keep a permanent address for internal funding flows
* **Recurring deposits** -- trusted payers send to the same address each time

---

# Webhooks

Source: https://docs.halfin.xyz/webhooks
Markdown: https://docs.halfin.xyz/md/webhooks

import { Callout } from 'fumadocs-ui/components/callout';
import { Step, Steps } from 'fumadocs-ui/components/steps';

Webhooks deliver event notifications to your server via HTTP POST. Use them for server-side order fulfillment instead of relying on polling alone.

## Event types

| Event                      | Description                                         |
| -------------------------- | --------------------------------------------------- |
| `invoice.confirming`       | Payment detected, awaiting blockchain confirmations |
| `invoice.paid`             | Invoice payment is fully paid and confirmed         |
| `invoice.overpaid`         | Customer sent more than the requested amount        |
| `invoice.underpaid`        | Customer sent less than the requested amount        |
| `invoice.expired`          | Invoice expired without sufficient payment          |
| `invoice.late_deposit`     | Deposit received after invoice expiry               |
| `invoice.deposit_reversed` | Confirmed deposit reversed                          |
| `balance.credited`         | Merchant balance was credited and is spendable      |
| `payout.completed`         | Payout confirmed on-chain                           |
| `payout.failed`            | Payout failed and reserved funds were released      |

The dashboard **Send Test** action sends a `test` delivery to active webhook endpoints in the selected environment. `test` is not a subscribable business event.

## Delivery

* **Method:** POST with JSON body
* **Retries:** Up to 10 attempts with exponential backoff
* **Timeout:** Your endpoint should respond within 15 seconds
* **Success:** Any `2xx` status code is treated as successful delivery

<Callout type="info">
  Webhook delivery is at least once. `balance.credited` includes `event_id`; invoice and payout handlers should de-duplicate by event type plus the invoice, payout, or payment IDs in `data`.
</Callout>

## Headers

Every webhook request includes:

```text
X-Halfin-Signature: t=1735689900,v1=5d41402abc4b2a76b9719d...
X-Halfin-Event: invoice.paid
Content-Type: application/json
```

## Setup

<Steps>
  <Step>
    ### Open dashboard webhooks

    Go to **Settings -> Webhooks** in the halfin dashboard.
  </Step>

  <Step>
    ### Add your endpoint URL

    Paste your HTTPS endpoint that will receive events.
  </Step>

  <Step>
    ### Subscribe to events

    Select the event types you care about. Use `invoice.paid` to track payment confirmation and `balance.credited` to fulfill orders from spendable merchant balance.
  </Step>

  <Step>
    ### Copy the webhook secret

    Use it for [signature verification](/webhooks/signature-verification).
  </Step>
</Steps>

## Payload schemas

For the generated JSON schema of each webhook event payload, see the [Webhook Schemas](/api/webhooks) section.

Use `invoice.paid` to track payment confirmation. Use `balance.credited` when fulfillment depends on spendable merchant balance availability.

---

# Signature Verification

Source: https://docs.halfin.xyz/webhooks/signature-verification
Markdown: https://docs.halfin.xyz/md/webhooks/signature-verification

Every webhook includes an `X-Halfin-Signature` header. Always verify this signature before processing the event.

## Signature format

```text
X-Halfin-Signature: t=1735689900,v1=5d41402abc4b2a76b9719d911017c592
```

| Part | Description               |
| ---- | ------------------------- |
| `t`  | Unix timestamp in seconds |
| `v1` | HMAC-SHA256 hex digest    |

## Algorithm

```text
HMAC-SHA256(webhook_secret, "{timestamp}.{raw_body}")
```

1. Extract `t` and `v1` from the header.
2. Compute `expected = HMAC-SHA256(secret, "{t}.{raw_body}")`.
3. Compare `expected` with `v1` using constant-time comparison.
4. Reject if `|now - t| > 300` seconds.

## With the SDK

```ts
import { verifySignature } from '@halfin/sdk-merchant';

const isValid = verifySignature({
  signature: req.headers['x-halfin-signature'] as string,
  body: rawBody,
  secret: process.env.HALFIN_WEBHOOK_SECRET!,
  toleranceSeconds: 300,
});
```

## Raw Node.js

```ts
import { createHmac, timingSafeEqual } from 'node:crypto';

function verify(signatureHeader: string, rawBody: Buffer, secret: string): boolean {
  const parts = Object.fromEntries(
    signatureHeader.split(',').map((part) => {
      const [key, ...value] = part.split('=');
      return [key, value.join('=')];
    }),
  );

  const timestamp = parts.t;
  const provided = parts.v1;

  if (!timestamp || !provided) return false;
  if (!/^\d+$/.test(timestamp) || !/^[0-9a-f]{64}$/i.test(provided)) return false;

  const age = Math.abs(Date.now() / 1000 - Number(timestamp));
  if (age > 300) return false;

  const expected = createHmac('sha256', secret)
    .update(`${timestamp}.`)
    .update(rawBody)
    .digest('hex');

  return timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(provided, 'hex'));
}
```

## Common mistakes

| Mistake                            | Fix                                                  |
| ---------------------------------- | ---------------------------------------------------- |
| Parsing JSON before computing HMAC | Use the raw request body bytes                       |
| Using `===` to compare signatures  | Use constant-time comparison                         |
| Skipping timestamp checks          | Validate `t` is within 300 seconds                   |
| Using the API key as the secret    | Use the webhook secret from **Settings -> Webhooks** |

---

# Cancel a pending invoice.

Source: https://docs.halfin.xyz/api/merchant/cancelInvoice
Markdown: https://docs.halfin.xyz/md/api/merchant/cancelInvoice

{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Cancels an invoice that has not yet received payment. Only invoices
in `pending` status can be cancelled.

<APIPage document={"./openapi/openapi.public.yaml"} operations={[{"path":"/v1/invoices/{invoiceID}/cancel","method":"post"}]} webhooks={[]} hasHead={false} />

---

# Cancel a payout.

Source: https://docs.halfin.xyz/api/merchant/cancelPayout
Markdown: https://docs.halfin.xyz/md/api/merchant/cancelPayout

{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Cancels a payout in `pending_approval` or `approved` status. Payouts
that are already processing or completed cannot be cancelled.

<APIPage document={"./openapi/openapi.public.yaml"} operations={[{"path":"/v1/payouts/{payoutID}/cancel","method":"post"}]} webhooks={[]} hasHead={false} />

---

# Create a permanent static deposit address.

Source: https://docs.halfin.xyz/api/merchant/createAddress
Markdown: https://docs.halfin.xyz/md/api/merchant/createAddress

{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Creates a new static deposit address for the specified currency.
Deposits to this address auto-create invoices. Static addresses are
deposit rails for repeat deposits, not checkout sessions.

<APIPage document={"./openapi/openapi.public.yaml"} operations={[{"path":"/v1/addresses","method":"post"}]} webhooks={[]} hasHead={false} />

---

# Create a new payment invoice.

Source: https://docs.halfin.xyz/api/merchant/createInvoice
Markdown: https://docs.halfin.xyz/md/api/merchant/createInvoice

{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Creates an invoice with a deposit address for the specified currency.
Returns a payment URL in `checkout_url` that the customer can visit
to pay.

Idempotency: when `idempotency_key` is supplied, a replay with the
same key but a different request body returns `422` with code
`idempotency_key_mismatch`. Replays with the same body are
idempotent and return the original invoice.

<APIPage document={"./openapi/openapi.public.yaml"} operations={[{"path":"/v1/invoices","method":"post"}]} webhooks={[]} hasHead={false} />

---

# Create a new payout.

Source: https://docs.halfin.xyz/api/merchant/createPayout
Markdown: https://docs.halfin.xyz/md/api/merchant/createPayout

{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Initiates a withdrawal to the specified destination address. The
payout enters `pending_approval` status and must be approved via
the dashboard before execution.

<APIPage document={"./openapi/openapi.public.yaml"} operations={[{"path":"/v1/payouts","method":"post"}]} webhooks={[]} hasHead={false} />

---

# Create a refund for a paid invoice.

Source: https://docs.halfin.xyz/api/merchant/createRefund
Markdown: https://docs.halfin.xyz/md/api/merchant/createRefund

{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Creates a merchant-funded refund to a supplied destination address
using the existing payout rail. The refund amount is capped by the
invoice's credited merchant balance for the same asset. The merchant
is charged the refund amount, the configured refund fee, and the
actual on-chain network fee reported when the linked payout completes.

<APIPage document={"./openapi/openapi.public.yaml"} operations={[{"path":"/v1/refunds","method":"post"}]} webhooks={[]} hasHead={false} />

---

# Get a single static deposit address.

Source: https://docs.halfin.xyz/api/merchant/getAddress
Markdown: https://docs.halfin.xyz/md/api/merchant/getAddress

{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

<APIPage document={"./openapi/openapi.public.yaml"} operations={[{"path":"/v1/addresses/{addressID}","method":"get"}]} webhooks={[]} hasHead={false} />

---

# Get a single invoice with payment details.

Source: https://docs.halfin.xyz/api/merchant/getInvoice
Markdown: https://docs.halfin.xyz/md/api/merchant/getInvoice

{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Returns the invoice and its associated on-chain payments. The
invoice must belong to the authenticated merchant and match the
API key's environment. Use this endpoint for server-side status
checks before fulfillment.

<APIPage document={"./openapi/openapi.public.yaml"} operations={[{"path":"/v1/invoices/{invoiceID}","method":"get"}]} webhooks={[]} hasHead={false} />

---

# Get ledger history for a specific currency.

Source: https://docs.halfin.xyz/api/merchant/getLedger
Markdown: https://docs.halfin.xyz/md/api/merchant/getLedger

{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Returns a paginated list of ledger entries (deposits, fees, payouts)
for the given currency. Supports filtering by entry type.

<APIPage document={"./openapi/openapi.public.yaml"} operations={[{"path":"/v1/balances/{currency}/ledger","method":"get"}]} webhooks={[]} hasHead={false} />

---

# Get a single payout.

Source: https://docs.halfin.xyz/api/merchant/getPayout
Markdown: https://docs.halfin.xyz/md/api/merchant/getPayout

{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Returns the payout detail. The payout must belong to the authenticated
merchant and match the API key's environment.

<APIPage document={"./openapi/openapi.public.yaml"} operations={[{"path":"/v1/payouts/{payoutID}","method":"get"}]} webhooks={[]} hasHead={false} />

---

# Merchant API

Source: https://docs.halfin.xyz/api/merchant
Markdown: https://docs.halfin.xyz/md/api/merchant

{/* This file was generated by the docs generation command. Do not edit directly. */}

Invoices, payouts, refunds, addresses, and balances for API-key integrations.

## Endpoints

* [Create a new payment invoice.](/api/merchant/createInvoice) `POST /v1/invoices`
* [List invoices for the authenticated merchant.](/api/merchant/listInvoices) `GET /v1/invoices`
* [Get a single invoice with payment details.](/api/merchant/getInvoice) `GET /v1/invoices/{invoiceID}`
* [Cancel a pending invoice.](/api/merchant/cancelInvoice) `POST /v1/invoices/{invoiceID}/cancel`
* [List all currency balances for the authenticated merchant.](/api/merchant/listBalances) `GET /v1/balances`
* [Get ledger history for a specific currency.](/api/merchant/getLedger) `GET /v1/balances/{currency}/ledger`
* [Create a refund for a paid invoice.](/api/merchant/createRefund) `POST /v1/refunds`
* [Create a new payout.](/api/merchant/createPayout) `POST /v1/payouts`
* [List payouts for the authenticated merchant.](/api/merchant/listPayouts) `GET /v1/payouts`
* [Get a single payout.](/api/merchant/getPayout) `GET /v1/payouts/{payoutID}`
* [Cancel a payout.](/api/merchant/cancelPayout) `POST /v1/payouts/{payoutID}/cancel`
* [Create a permanent static deposit address.](/api/merchant/createAddress) `POST /v1/addresses`
* [List static deposit addresses.](/api/merchant/listAddresses) `GET /v1/addresses`
* [Get a single static deposit address.](/api/merchant/getAddress) `GET /v1/addresses/{addressID}`
* [List invoices for a static address.](/api/merchant/listAddressInvoices) `GET /v1/addresses/{addressID}/invoices`

---

# List invoices for a static address.

Source: https://docs.halfin.xyz/api/merchant/listAddressInvoices
Markdown: https://docs.halfin.xyz/md/api/merchant/listAddressInvoices

{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Returns a paginated list of invoices auto-created from deposits
to the specified static address.

<APIPage document={"./openapi/openapi.public.yaml"} operations={[{"path":"/v1/addresses/{addressID}/invoices","method":"get"}]} webhooks={[]} hasHead={false} />

---

# List static deposit addresses.

Source: https://docs.halfin.xyz/api/merchant/listAddresses
Markdown: https://docs.halfin.xyz/md/api/merchant/listAddresses

{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Returns a paginated list of static addresses for the authenticated
merchant. Supports filtering by currency.

<APIPage document={"./openapi/openapi.public.yaml"} operations={[{"path":"/v1/addresses","method":"get"}]} webhooks={[]} hasHead={false} />

---

# List all currency balances for the authenticated merchant.

Source: https://docs.halfin.xyz/api/merchant/listBalances
Markdown: https://docs.halfin.xyz/md/api/merchant/listBalances

{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Returns one balance row per currency for the merchant tied to the
authenticating API key. The environment is resolved from the API
key that authenticated the request. Use balances and ledger entries
to reconcile spendable funds after invoice status checks.

<APIPage document={"./openapi/openapi.public.yaml"} operations={[{"path":"/v1/balances","method":"get"}]} webhooks={[]} hasHead={false} />

---

# List invoices for the authenticated merchant.

Source: https://docs.halfin.xyz/api/merchant/listInvoices
Markdown: https://docs.halfin.xyz/md/api/merchant/listInvoices

{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Returns a paginated list of invoices scoped to the API key's
environment. Supports filtering by status and currency.

<APIPage document={"./openapi/openapi.public.yaml"} operations={[{"path":"/v1/invoices","method":"get"}]} webhooks={[]} hasHead={false} />

---

# List payouts for the authenticated merchant.

Source: https://docs.halfin.xyz/api/merchant/listPayouts
Markdown: https://docs.halfin.xyz/md/api/merchant/listPayouts

{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Returns a paginated list of payouts scoped to the API key's environment.

<APIPage document={"./openapi/openapi.public.yaml"} operations={[{"path":"/v1/payouts","method":"get"}]} webhooks={[]} hasHead={false} />

---

# Get supported payment currencies.

Source: https://docs.halfin.xyz/api/public/getCurrencies
Markdown: https://docs.halfin.xyz/md/api/public/getCurrencies

{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Returns all available payment currencies with their network details
and confirmation requirements. Use it to decide which currencies and
networks can be offered for the selected environment.

<APIPage document={"./openapi/openapi.public.yaml"} operations={[{"path":"/v1/currencies","method":"get"}]} webhooks={[]} hasHead={false} />

---

# Get current exchange rates.

Source: https://docs.halfin.xyz/api/public/getRates
Markdown: https://docs.halfin.xyz/md/api/public/getRates

{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Returns USD exchange rates for all supported cryptocurrencies.
Response is a map keyed by currency code. Use it for display and
reconciliation context, not as a payment-status signal.

<APIPage document={"./openapi/openapi.public.yaml"} operations={[{"path":"/v1/rates","method":"get"}]} webhooks={[]} hasHead={false} />

---

# Public API

Source: https://docs.halfin.xyz/api/public
Markdown: https://docs.halfin.xyz/md/api/public

{/* This file was generated by the docs generation command. Do not edit directly. */}

Exchange rates and supported currency metadata.

## Endpoints

* [Get supported payment currencies.](/api/public/getCurrencies) `GET /v1/currencies`
* [Get current exchange rates.](/api/public/getRates) `GET /v1/rates`

---

# Webhooks

Source: https://docs.halfin.xyz/api/webhooks
Markdown: https://docs.halfin.xyz/md/api/webhooks

{/* This file was generated by the docs generation command. Do not edit directly. */}

Webhook event payload schemas delivered to merchant endpoints.

## Events

* [Invoice payment detected, awaiting confirmations.](/api/webhooks/webhookInvoiceConfirming) `invoice.confirming`
* [Invoice fully paid.](/api/webhooks/webhookInvoicePaid) `invoice.paid`
* [Invoice received more than the requested amount.](/api/webhooks/webhookInvoiceOverpaid) `invoice.overpaid`
* [Invoice expired with partial payment.](/api/webhooks/webhookInvoiceUnderpaid) `invoice.underpaid`
* [Invoice expired without sufficient payment.](/api/webhooks/webhookInvoiceExpired) `invoice.expired`
* [Late deposit received on an expired invoice.](/api/webhooks/webhookInvoiceLateDeposit) `invoice.late_deposit`
* [A previously confirmed deposit was reversed (e.g. chain reorg).](/api/webhooks/webhookInvoiceDepositReversed) `invoice.deposit_reversed`
* [Merchant balance credited.](/api/webhooks/webhookBalanceCredited) `balance.credited`
* [Refund created and funds reserved.](/api/webhooks/webhookRefundCreated) `refund.created`
* [Refund completed on-chain.](/api/webhooks/webhookRefundCompleted) `refund.completed`
* [Refund failed, reserved funds released.](/api/webhooks/webhookRefundFailed) `refund.failed`
* [Payout successfully broadcast and confirmed.](/api/webhooks/webhookPayoutCompleted) `payout.completed`
* [Payout failed, funds released.](/api/webhooks/webhookPayoutFailed) `payout.failed`

---

# Merchant balance credited.

Source: https://docs.halfin.xyz/api/webhooks/webhookBalanceCredited
Markdown: https://docs.halfin.xyz/md/api/webhooks/webhookBalanceCredited

{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Fired only after the merchant's available balance is credited.
Use this event as the fulfillment signal when balance
availability matters.

**Signature:** `X-Halfin-Signature: t={timestamp},v1={hmac}`
where `hmac = HMAC-SHA256(secret, "{timestamp}.{raw_body}")`.
Reject if `abs(now - timestamp) > 300` seconds.

<APIPage document={"./openapi/openapi.public.yaml"} operations={[]} webhooks={[{"name":"balance.credited","method":"post"}]} hasHead={false} />

---

# Invoice payment detected, awaiting confirmations.

Source: https://docs.halfin.xyz/api/webhooks/webhookInvoiceConfirming
Markdown: https://docs.halfin.xyz/md/api/webhooks/webhookInvoiceConfirming

{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Fired when the first on-chain payment is detected for an invoice.

**Signature:** `X-Halfin-Signature: t={timestamp},v1={hmac}`
where `hmac = HMAC-SHA256(secret, "{timestamp}.{raw_body}")`.
Reject if `abs(now - timestamp) > 300` seconds.

<APIPage document={"./openapi/openapi.public.yaml"} operations={[]} webhooks={[{"name":"invoice.confirming","method":"post"}]} hasHead={false} />

---

# A previously confirmed deposit was reversed (e.g. chain reorg).

Source: https://docs.halfin.xyz/api/webhooks/webhookInvoiceDepositReversed
Markdown: https://docs.halfin.xyz/md/api/webhooks/webhookInvoiceDepositReversed

{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Fired when a confirmed payment is dropped due to a blockchain
reorganization. The invoice's amount\_paid is recalculated.

**Signature:** `X-Halfin-Signature: t={timestamp},v1={hmac}`
where `hmac = HMAC-SHA256(secret, "{timestamp}.{raw_body}")`.
Reject if `abs(now - timestamp) > 300` seconds.

<APIPage document={"./openapi/openapi.public.yaml"} operations={[]} webhooks={[{"name":"invoice.deposit_reversed","method":"post"}]} hasHead={false} />

---

# Invoice expired without sufficient payment.

Source: https://docs.halfin.xyz/api/webhooks/webhookInvoiceExpired
Markdown: https://docs.halfin.xyz/md/api/webhooks/webhookInvoiceExpired

{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Fired when an invoice reaches its TTL without being fully paid.

**Signature:** `X-Halfin-Signature: t={timestamp},v1={hmac}`
where `hmac = HMAC-SHA256(secret, "{timestamp}.{raw_body}")`.
Reject if `abs(now - timestamp) > 300` seconds.

<APIPage document={"./openapi/openapi.public.yaml"} operations={[]} webhooks={[{"name":"invoice.expired","method":"post"}]} hasHead={false} />

---

# Late deposit received on an expired invoice.

Source: https://docs.halfin.xyz/api/webhooks/webhookInvoiceLateDeposit
Markdown: https://docs.halfin.xyz/md/api/webhooks/webhookInvoiceLateDeposit

{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Fired when a payment arrives after the invoice has already expired.
The merchant can decide whether to credit the customer.

**Signature:** `X-Halfin-Signature: t={timestamp},v1={hmac}`
where `hmac = HMAC-SHA256(secret, "{timestamp}.{raw_body}")`.
Reject if `abs(now - timestamp) > 300` seconds.

<APIPage document={"./openapi/openapi.public.yaml"} operations={[]} webhooks={[{"name":"invoice.late_deposit","method":"post"}]} hasHead={false} />

---

# Invoice received more than the requested amount.

Source: https://docs.halfin.xyz/api/webhooks/webhookInvoiceOverpaid
Markdown: https://docs.halfin.xyz/md/api/webhooks/webhookInvoiceOverpaid

{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Fired when the total paid exceeds the requested amount.

**Signature:** `X-Halfin-Signature: t={timestamp},v1={hmac}`
where `hmac = HMAC-SHA256(secret, "{timestamp}.{raw_body}")`.
Reject if `abs(now - timestamp) > 300` seconds.

<APIPage document={"./openapi/openapi.public.yaml"} operations={[]} webhooks={[{"name":"invoice.overpaid","method":"post"}]} hasHead={false} />

---

# Invoice fully paid.

Source: https://docs.halfin.xyz/api/webhooks/webhookInvoicePaid
Markdown: https://docs.halfin.xyz/md/api/webhooks/webhookInvoicePaid

{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Fired when the invoice amount is met and confirmations are sufficient.

**Signature:** `X-Halfin-Signature: t={timestamp},v1={hmac}`
where `hmac = HMAC-SHA256(secret, "{timestamp}.{raw_body}")`.
Reject if `abs(now - timestamp) > 300` seconds.

<APIPage document={"./openapi/openapi.public.yaml"} operations={[]} webhooks={[{"name":"invoice.paid","method":"post"}]} hasHead={false} />

---

# Invoice expired with partial payment.

Source: https://docs.halfin.xyz/api/webhooks/webhookInvoiceUnderpaid
Markdown: https://docs.halfin.xyz/md/api/webhooks/webhookInvoiceUnderpaid

{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Fired when an invoice expires after receiving a partial payment
below the underpayment threshold.

**Signature:** `X-Halfin-Signature: t={timestamp},v1={hmac}`
where `hmac = HMAC-SHA256(secret, "{timestamp}.{raw_body}")`.
Reject if `abs(now - timestamp) > 300` seconds.

<APIPage document={"./openapi/openapi.public.yaml"} operations={[]} webhooks={[{"name":"invoice.underpaid","method":"post"}]} hasHead={false} />

---

# Payout successfully broadcast and confirmed.

Source: https://docs.halfin.xyz/api/webhooks/webhookPayoutCompleted
Markdown: https://docs.halfin.xyz/md/api/webhooks/webhookPayoutCompleted

{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Fired when a payout is confirmed on-chain.

**Signature:** `X-Halfin-Signature: t={timestamp},v1={hmac}`
where `hmac = HMAC-SHA256(secret, "{timestamp}.{raw_body}")`.
Reject if `abs(now - timestamp) > 300` seconds.

<APIPage document={"./openapi/openapi.public.yaml"} operations={[]} webhooks={[{"name":"payout.completed","method":"post"}]} hasHead={false} />

---

# Payout failed, funds released.

Source: https://docs.halfin.xyz/api/webhooks/webhookPayoutFailed
Markdown: https://docs.halfin.xyz/md/api/webhooks/webhookPayoutFailed

{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Fired when a payout fails. Reserved funds are released back to the
merchant's available balance.

**Signature:** `X-Halfin-Signature: t={timestamp},v1={hmac}`
where `hmac = HMAC-SHA256(secret, "{timestamp}.{raw_body}")`.
Reject if `abs(now - timestamp) > 300` seconds.

<APIPage document={"./openapi/openapi.public.yaml"} operations={[]} webhooks={[{"name":"payout.failed","method":"post"}]} hasHead={false} />

---

# Refund completed on-chain.

Source: https://docs.halfin.xyz/api/webhooks/webhookRefundCompleted
Markdown: https://docs.halfin.xyz/md/api/webhooks/webhookRefundCompleted

{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Fired when the linked payout completes. The payload includes the
transaction hash and the actual network fee charged to the merchant.

**Signature:** `X-Halfin-Signature: t={timestamp},v1={hmac}`
where `hmac = HMAC-SHA256(secret, "{timestamp}.{raw_body}")`.
Reject if `abs(now - timestamp) > 300` seconds.

<APIPage document={"./openapi/openapi.public.yaml"} operations={[]} webhooks={[{"name":"refund.completed","method":"post"}]} hasHead={false} />

---

# Refund created and funds reserved.

Source: https://docs.halfin.xyz/api/webhooks/webhookRefundCreated
Markdown: https://docs.halfin.xyz/md/api/webhooks/webhookRefundCreated

{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Fired when a refund is created and the linked payout is queued for
execution.

**Signature:** `X-Halfin-Signature: t={timestamp},v1={hmac}`
where `hmac = HMAC-SHA256(secret, "{timestamp}.{raw_body}")`.
Reject if `abs(now - timestamp) > 300` seconds.

<APIPage document={"./openapi/openapi.public.yaml"} operations={[]} webhooks={[{"name":"refund.created","method":"post"}]} hasHead={false} />

---

# Refund failed, reserved funds released.

Source: https://docs.halfin.xyz/api/webhooks/webhookRefundFailed
Markdown: https://docs.halfin.xyz/md/api/webhooks/webhookRefundFailed

{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}

Fired when the linked payout fails and the refund reservation is
released back to the merchant balance.

**Signature:** `X-Halfin-Signature: t={timestamp},v1={hmac}`
where `hmac = HMAC-SHA256(secret, "{timestamp}.{raw_body}")`.
Reject if `abs(now - timestamp) > 300` seconds.

<APIPage document={"./openapi/openapi.public.yaml"} operations={[]} webhooks={[{"name":"refund.failed","method":"post"}]} hasHead={false} />

---

# TypeScript SDK

Source: https://docs.halfin.xyz/guides/sdks/typescript
Markdown: https://docs.halfin.xyz/md/guides/sdks/typescript

The `@halfin/sdk-merchant` package provides typed methods for the halfin API and webhook signature verification.

## Installation

```bash
pnpm add @halfin/sdk-merchant
```

## Quick Start

```typescript
import { createHalfin } from '@halfin/sdk-merchant';

const client = createHalfin({
  apiKey: process.env.HALFIN_API_KEY!,
  baseUrl: 'https://dashboard.halfin.xyz/api',
});
```

## Create an Invoice

```typescript
import { createInvoice } from '@halfin/sdk-merchant';

const { data } = await createInvoice({
  client,
  body: {
    currency: 'BTC',
    amount: '0.001',
    description: 'Order #1234',
    metadata: { order_id: '1234' },
  },
});

console.log(data.id);              // "00000000-..."
console.log(data.checkout_url);    // "https://checkout.halfin.xyz/..."
```

## Other Methods

```typescript
import {
  cancelInvoice,
  createAddress,
  getInvoice,
  getRates,
  listBalances,
  listInvoices,
} from '@halfin/sdk-merchant';

// List invoices
const invoices = await listInvoices({
  client,
  query: { status: 'paid', limit: 10 },
});

// Get a single invoice
const invoice = await getInvoice({
  client,
  path: { invoiceID: '00000000-0000-0000-0000-000000000001' },
});

// Cancel a pending invoice
await cancelInvoice({
  client,
  path: { invoiceID: '00000000-0000-0000-0000-000000000001' },
});

// Create a static address
const address = await createAddress({
  client,
  body: { currency: 'ETH', label: 'Account top-up' },
});

// List balances
const balances = await listBalances({ client });

// Get exchange rates
const rates = await getRates({ client });
```

## Webhook Verification

```typescript
import { verifySignature } from '@halfin/sdk-merchant';

const isValid = verifySignature({
  signature: req.headers['x-halfin-signature'] as string,
  body: rawBody,
  secret: process.env.HALFIN_WEBHOOK_SECRET!,
  toleranceSeconds: 300, // reject events older than 5 minutes
});
```
