> For the complete documentation index, see [llms.txt](https://developer.branta.pro/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developer.branta.pro/setup/platforms/custom-integration.md).

# Custom Integration

If none of the [no-code gateway options](/setup/platforms/payment-gateway-options.md) fit — you run your own checkout, a custom processor, or just want full control over the request — wire Branta in directly. There are two equivalent paths: the SDK (recommended) or a raw HTTP `POST`.

### Prerequisites

* You've completed [Platform onboarding](/setup/platforms.md) — account, approved platform, and an API key.
* You know which [environment](/tech/environments.md) you're hitting (`staging` while testing, `production` for live traffic).
* Your code can hold an API key securely — server-side only, never in a browser bundle or mobile binary.

### What "integrating" means

Every time your platform issues a payment destination (on-chain address, Lightning invoice, Lightning address, etc.) to a sender, `POST` it to Branta. From that moment, any wallet or scanner that looks up that destination will see your name and logo. The full request/response shape is documented under [Adding Payments](/tech/api/v2/adding-payments.md).

Two privacy postures:

* **Plain** — you send the destination as-is. Branta stores it and serves it back on lookup. Simplest, works for any [destination type](/tech/api/v2/adding-payments.md).
* **Zero-Knowledge** — your code encrypts the destination with a per-payment secret before posting. Branta only ever sees ciphertext; only senders with the secret (delivered via the QR code) can decrypt. See [Zero Knowledge](/tech/api/v2/adding-payments.md#zero-knowledge) for the algorithm. The SDKs handle this transparently when `isZk: true` is set on a destination.

### Option 1: Use the SDK (recommended)

The SDKs handle ZK encryption, secret generation, verify-URL building, and request shaping for you.

{% tabs %}
{% tab title="JavaScript" %}
SDK: [`@branta-ops/branta`](https://www.npmjs.com/package/@branta-ops/branta) ([source](https://github.com/BrantaOps/branta-js))

```ts
import { BrantaServerBaseUrl } from "@branta-ops/branta";
import { BrantaService } from "@branta-ops/branta/v2";
import { DestinationType } from "@branta-ops/branta";
import { PrivacyMode } from "@branta-ops/branta";

const service = new BrantaService({
  baseUrl: BrantaServerBaseUrl.Production,
  defaultApiKey: process.env.BRANTA_API_KEY!,
  privacy: PrivacyMode.Loose,
});

// Called whenever your platform issues a destination to a sender.
async function publishDestination(address: string) {
  const { payment, secret, verifyUrl } = await service.addPayment({
    description: "Invoice #12345",
    destinations: [
      {
        value: address,
        type: DestinationType.BitcoinAddress,
        isPrimary: true,
        isZk: true, // set false to publish in plain
      },
    ],
    ttl: 3600, // optional — seconds until Branta forgets this destination
    metadata: "order-12345", // optional — your own correlation id
  });

  // For ZK: persist `secret` alongside your order — you need it to build
  // the `branta_secret` query param when rendering the payment QR / link.
  // `verifyUrl` is the public Branta verify page for this payment.
  return { payment, secret, verifyUrl };
}
```

Privacy mode interacts with `isZk` on `addPayment`: in `strict` mode (the wallet default) all destinations must be ZK or the call throws. Strict is also the SDK default — platforms typically set `privacy` explicitly to `loose` and choose per-destination.
{% endtab %}

{% tab title=".NET" %}
SDK: [`Branta`](https://www.nuget.org/packages/Branta) ([source](https://github.com/BrantaOps/branta-dotnet))

```cs
using Branta.Classes;
using Branta.Enums;
using Branta.V2.Extensions;
using Branta.V2.Interfaces;
using Branta.V2.Models;

services.ConfigureBrantaServices(new BrantaClientOptions
{
    BaseUrl       = BrantaServerBaseUrl.Production,
    DefaultApiKey = configuration["Branta:ApiKey"],
    Privacy       = PrivacyMode.Loose,
});

public class CheckoutService(IBrantaService brantaService)
{
    public async Task<(Payment Payment, string Secret, string VerifyUrl)> PublishDestinationAsync(string address)
    {
        var payment = new Payment
        {
            Description = "Invoice #12345",
            Destinations =
            [
                new Destination
                {
                    Value     = address,
                    Type      = DestinationType.BitcoinAddress,
                    IsPrimary = true,
                    IsZk      = true, // set false to publish in plain
                }
            ],
            TTL      = 3600,         // optional
            Metadata = "order-12345" // optional
        };

        var (savedPayment, secret, verifyUrl) = await brantaService.AddPaymentAsync(payment);

        // For ZK: persist `secret` alongside your order — you need it to build
        // the `branta_secret` query param when rendering the payment QR / link.
        return (savedPayment, secret, verifyUrl);
    }
}
```

{% endtab %}
{% endtabs %}

`ttl` accepts 30 seconds minimum, 1 year maximum; the server default is 7 days (604800 s). `metadata` is stored verbatim as a string — Branta does not parse it. The SDKs' `PaymentBuilder.addMetadata(key, value)` helpers build a JSON object if you want structured fields.

### Option 2: Raw HTTP

If your stack isn't covered by an SDK, `POST` directly. Same endpoint, same shape — you handle ZK encryption yourself if you want it (see [Zero Knowledge](/tech/api/v2/adding-payments.md#zero-knowledge) for the JavaScript reference implementation).

```bash
curl -X POST https://guardrail.branta.pro/v2/payments \
  -H "Authorization: Bearer $BRANTA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "Invoice #12345",
    "destinations": [
      {
        "value": "bc1qu3k6geqdjncaarsu2vq56tt8php5vsug9kasmq",
        "type": "bitcoin_address",
        "primary": true,
        "zk": false
      }
    ],
    "ttl": 3600,
    "metadata": "order-12345"
  }'
```

Full schema (all destination types, ZK fields, response shape) is on the [Adding Payments](/tech/api/v2/adding-payments.md) reference. Authentication header rules: [Authentication](/tech/authentication.md).

### Surfacing Branta in your UI

Once the destination is posted, link senders to the [`verifyUrl`](/tech/api/v2/adding-payments.md) returned by the API. You can also use [website badges](/design/website-badge.md) to indicate Branta protection at checkout, or build a custom UI per the [UI guidelines](/design/ui.md).

#### QR code / payment URI (ZK on-chain)

For **plain** destinations, the payment QR is a standard BIP-21 URI (`bitcoin:<address>`) — no Branta params needed.

For **ZK on-chain** destinations, the wallet needs two extra query parameters to look up and decrypt the record:

| Param           | Source                            | Description                                              |
| --------------- | --------------------------------- | -------------------------------------------------------- |
| `branta_id`     | `payment.destinations[n].value`   | URL-encoded encrypted ciphertext stored in Branta        |
| `branta_secret` | `secret` returned by `addPayment` | Decryption key — delivered to the sender via the QR only |

```
bitcoin:<plain_address>?branta_id=<url_encoded_branta_id>&branta_secret=<secret>
```

The plain address stays in the URI so the wallet knows where to send; `branta_id` and `branta_secret` are only for the Branta lookup. Wallets that don't recognise these params ignore them (standard BIP-21 unknown-param behaviour).

Example — building the URI from the SDK response:

```ts
const { payment, secret } = await service.addPayment({
  destinations: [{ value: address, type: DestinationType.BitcoinAddress, isPrimary: true, isZk: true }],
  // ...
});

const dest = payment.destinations.find(d => d.isPrimary);
const qrUri = `bitcoin:${address}?branta_id=${encodeURIComponent(dest.value)}&branta_secret=${secret}`;
// Encode qrUri into a QR code and render it at checkout.
```

{% hint style="info" %}
Lightning destinations (plain or ZK) use the standard `lightning:<invoice>` URI — no extra params needed. The wallet SDK handles the lookup automatically.
{% endhint %}

### Testing

* Use [staging](/tech/environments.md) for any non-live work — `https://staging.guardrail.branta.pro` with a staging API key from the staging dashboard.
* Validate the round-trip with [scan.branta.pro](https://scan.branta.pro/scan) and the [example QR codes](/setup/wallets/example-qr-codes.md) — your wallet-side rendering should match.
* The SDKs expose `isApiKeyValid()` / `IsApiKeyValidAsync()` for a cheap credential health check.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://developer.branta.pro/setup/platforms/custom-integration.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
