# Branta

<figure><img src="/files/H3jLv5vX7UyRNWlKF2Yk" alt=""><figcaption></figcaption></figure>

**Pay with certainty.**

Higher conversion. Higher retention. Less fraud.

Eliminate phishing, supply chain, address swap, and man-in-the-middle vulnerabilities at point of transaction.

Easily integrate with existing Bitcoin, Lightning, Ark, Spark, Stables, and more payment flows, without leaking privacy.


# Setup

Pick the integration that matches what you're building.

Branta plugs into three places in the Bitcoin payments stack. Pick the one that matches your role — the onboarding requirements and integration shape differ.

### [Wallets](/setup/wallets)

For wallet developers who want their users to see *who* they are paying before broadcast.

* **Account required:** None. Wallets read Branta data; they do not publish.
* **Integration:** Drop in the [SDK](/tech/sdk) (`branta-js`, `branta-dotnet`, `branta-python`, `branta-dart`, `branta-kotlin`, `branta-rust`) and call `getPaymentsByQrCode` / `getPayments` from your send flow.

### [Platforms](/setup/platforms)

For merchants, payment processors, and apps that issue Bitcoin payment destinations and want their counterparty info (name, logo) shown to senders.

* **Account required:** Yes — sign up at [guardrail.branta.pro](https://guardrail.branta.pro), submit a platform request, and wait for approval.
* **Integration:** Install a turn-key [Payment Gateway](/setup/platforms/payment-gateway-options) (BTCPay Server, Zaprite, Take My Sats) — no code — or build a [Custom Integration](/setup/platforms/custom-integration) using the SDKs or raw HTTP.

### [Parent Platforms](/setup/parent-platforms)

For services that integrate Branta on behalf of many merchants and `POST` payments for each. Each merchant has their own Branta platform and API key; the parent service signs every request with its own HMAC secret to prove the call originated from it.

* **Account required:** Yes — same flow as a regular platform, plus a Branta admin must enable the `parent_platform` feature on your account.
* **Integration:** `POST` to `/payments` using the **merchant's** API key in `Authorization`, signed with **your** HMAC secret in `X-HMAC-Signature` / `X-HMAC-Timestamp`.

{% hint style="info" %}
Self-hosted, single-tenant deployments like **BTCPay Server** are not parent platforms — each BTCPay store is its own Branta platform with its own API key and no HMAC layer.
{% endhint %}

### Using AI to implement?

Copy a prompt from the [SDKs](/tech/sdk) page and paste it into your AI agent. The prompts install the correct package and walk the agent through the Integration Guide automatically.

### Not sure which one you are?

| You are…                                                                                               | You want…                                                                                                                                                                            |
| ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Building a wallet app                                                                                  | [Wallets](/setup/wallets)                                                                                                                                                            |
| Running a single store or service that takes Bitcoin                                                   | [Platforms](/setup/platforms) — start with a [Payment Gateway](/setup/platforms/payment-gateway-options) if one fits, else [Custom Integration](/setup/platforms/custom-integration) |
| Running a payment app that issues destinations on behalf of many merchants (ex: Zaprite, Take My Sats) | [Parent Platforms](/setup/parent-platforms)                                                                                                                                          |


# Wallets

Embed Branta in your wallet so users see who they are paying before they hit send.

This page is for wallet developers integrating Branta into a send flow. Embedding Branta lets your users see counterparty name and logo before broadcast (and again in payment history afterward) turning an address or invoice into a recognizable payee.

### Why integrate Branta

* Users see *who* they are paying before broadcast (counterparty name and logo), not just an address or invoice.
* Eliminates phishing, supply-chain, address-swap, and man-in-the-middle attacks at point of transaction.
* Works across Bitcoin onchain, Lightning (bolt11, bolt12, lnurl, ln address), Ark, and Silent Payments.
* Privacy-preserving — `strict` mode never sends plain-text onchain addresses to Branta.
* No permission needed — and one network call per scan or paste.
* Reuse the same counterparty name and logo in [payment history](#payment-history), so a past payment reads as a recognizable payee, not an address.

### How Branta works for wallets

Your wallet hands raw QR text or user-pasted text to the Branta SDK. The SDK calls Branta in `strict` privacy mode: onchain destinations only resolve when the QR carries `branta_id` and `branta_secret` (ZK-encoded), while bolt11, Ark (`ark1…`), and Silent Payments (`sp1…` / `tsp1…`) resolve using hash-ZK — no secret needed. On a hit, render the returned counterparty name and logo before the user confirms send. On a miss or error, show nothing — a missing record just means the destination was never posted to Branta.

See [SDK](/tech/sdk), [Environments](/tech/environments), and [Authentication](/tech/authentication) for deeper reference.

### Integrate

{% tabs %}
{% tab title="JavaScript" %}
SDK: [branta-js](https://github.com/BrantaOps/branta-js)

Wallets should use `strict` privacy mode. Two flows are supported:

* **QR scan**: call `getPaymentsByQrCode` with the raw QR text. This handles both on-chain (when the QR includes `branta_id` / `branta_secret`) and lightning destinations.
* **Copy/paste**: call `getPayments` with the pasted text. Plain-text on-chain addresses will not return results in strict mode — they must be ZK-encoded. bolt11, Ark, and Silent Payments work as plain text (hash-ZK — no secret needed).

Always catch errors and show nothing on not-found — a missing record just means the address was not posted to Branta.

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

const service = new BrantaService({
  baseUrl: BrantaServerBaseUrl.Production,
  privacy: 'strict',
});

async function lookup(input: string, isQrCode: boolean) {
  try {
    const result = isQrCode
      ? await service.getPaymentsByQrCode(input)
      : await service.getPayments(input);

    if (result.payments.length === 0) {
      // Not found — show nothing. The address may simply not exist in Branta.
      return;
    }

    // Render result.payments (name and logo)
  } catch {
    // Swallow errors — never surface a "not found" or lookup failure to the user.
  }
}
```

{% endtab %}

{% tab title=".NET" %}
SDK: [branta-dotnet](https://github.com/BrantaOps/branta-dotnet)

Wallets should use `Strict` privacy mode. Two flows are supported:

* **QR scan**: call `GetPaymentsByQrCodeAsync` with the raw QR text. This handles both on-chain (when the QR includes `branta_id` / `branta_secret`) and lightning destinations.
* **Copy/paste**: call `GetPaymentsAsync` with the pasted text. Plain-text on-chain addresses will not return results in strict mode — they must be ZK-encoded. bolt11, Ark, and Silent Payments work as plain text (hash-ZK — no secret needed).

Always catch errors and show nothing on not-found — a missing record just means the address was not posted to Branta.

```cs
using Branta.V2.Extensions;
using Branta.V2.Interfaces;

services.ConfigureBrantaServices(new BrantaClientOptions() {
    BaseUrl = BrantaServerBaseUrl.Production,
    Privacy = PrivacyMode.Strict
});
```

```cs
public class Example(IBrantaService brantaService)
{
    public async Task LookupAsync(string input, bool isQrCode)
    {
        try
        {
            var result = isQrCode
                ? await brantaService.GetPaymentsByQrCodeAsync(input)
                : await brantaService.GetPaymentsAsync(input);

            if (result.Payments.Count == 0)
            {
                // Not found — show nothing. The address may simply not exist in Branta.
                return;
            }

            // Render result.Payments (name and logo)
        }
        catch
        {
            // Swallow errors — never surface a "not found" or lookup failure to the user.
        }
    }
}
```

{% endtab %}

{% tab title="Python" %}
SDK: [branta-python](https://github.com/BrantaOps/branta-python)

Wallets should use `PrivacyMode.Strict`. Two flows are supported:

* **QR scan**: call `get_payments_by_qr_code` with the raw QR text. This handles both on-chain (when the QR includes `branta_id` / `branta_secret`) and lightning destinations.
* **Copy/paste**: call `get_payments` with the pasted text. Plain-text on-chain addresses will not return results in strict mode — they must be ZK-encoded. bolt11, Ark, and Silent Payments work as plain text (hash-ZK — no secret needed).

Always catch errors and show nothing on not-found — a missing record just means the address was not posted to Branta.

```python
from branta.enums import BrantaServerBaseUrl, PrivacyMode
from branta.options import BrantaClientOptions
from branta.v2 import BrantaService

service = BrantaService(BrantaClientOptions(
    base_url=BrantaServerBaseUrl.Production,
    privacy=PrivacyMode.Strict,
))

async def lookup(input: str, is_qr_code: bool) -> None:
    try:
        result = (
            await service.get_payments_by_qr_code(input)
            if is_qr_code
            else await service.get_payments(input)
        )

        if not result.payments:
            # Not found — show nothing. The address may simply not exist in Branta.
            return

        # Render result.payments (name and logo)
    except Exception:
        # Swallow errors — never surface a "not found" or lookup failure to the user.
        pass
```

{% endtab %}

{% tab title="Dart" %}
SDK: [branta-dart](https://github.com/BrantaOps/branta-dart)

Wallets should use `PrivacyMode.strict`. Two flows are supported:

* **QR scan**: call `getPaymentsByQrCodeAsync` with the raw QR text. This handles both on-chain (when the QR includes `branta_id` / `branta_secret`) and lightning destinations.
* **Copy/paste**: call `getPaymentsAsync` with the pasted text. Plain-text on-chain addresses will not return results in strict mode — they must be ZK-encoded. bolt11, Ark, and Silent Payments work as plain text (hash-ZK — no secret needed).

Always catch errors and show nothing on not-found — a missing record just means the address was not posted to Branta.

```dart
import 'package:branta/branta.dart';
import 'package:http/http.dart' as http;

final options = BrantaClientOptions(
  baseUrl: BrantaServerBaseUrl.production,
  privacy: PrivacyMode.strict,
);
final brantaClient = BrantaClient(httpClient: http.Client(), defaultOptions: options);
final service = BrantaService(
  client: brantaClient,
  aesEncryption: AesEncryptionService(),
  defaultOptions: options,
);

Future<void> lookup(String input, bool isQrCode) async {
  try {
    final result = isQrCode
        ? await service.getPaymentsByQrCodeAsync(input)
        : await service.getPaymentsAsync(input);

    if (result.payments.isEmpty) {
      // Not found — show nothing. The address may simply not exist in Branta.
      return;
    }

    // Render result.payments (name and logo)
  } catch (_) {
    // Swallow errors — never surface a "not found" or lookup failure to the user.
  }
}
```

{% endtab %}

{% tab title="Kotlin" %}
SDK: [branta-kotlin](https://github.com/BrantaOps/branta-kotlin)

Wallets should use `PrivacyMode.Strict`. Two flows are supported:

* **QR scan**: call `getPaymentsByQrCode` with the raw QR text. This handles both on-chain (when the QR includes `branta_id` / `branta_secret`) and lightning destinations.
* **Copy/paste**: call `getPayments` with the pasted text. Plain-text on-chain addresses will not return results in strict mode — they must be ZK-encoded. bolt11, Ark, and Silent Payments work as plain text (hash-ZK — no secret needed).

Always catch errors and show nothing on not-found — a missing record just means the address was not posted to Branta.

```kotlin
val service = BrantaService(
    BrantaClientOptions(
        baseUrl = BrantaServerBaseUrl.Production,
        privacy = PrivacyMode.Strict
    )
)

// In a coroutine scope (e.g. viewModelScope):
suspend fun lookup(input: String, isQrCode: Boolean) {
    try {
        val result = if (isQrCode) {
            service.getPaymentsByQrCode(input)
        } else {
            service.getPayments(input)
        }

        if (result.payments.isEmpty()) {
            // Not found — show nothing. The address may simply not exist in Branta.
            return
        }

        // Render result.payments (name and logo)
    } catch (e: Exception) {
        // Swallow errors — never surface a "not found" or lookup failure to the user.
    }
}
```

{% endtab %}

{% tab title="Rust" %}
SDK: [branta-rust](https://github.com/BrantaOps/branta-rust)

Wallets should use `PrivacyMode::Strict`. Two flows are supported:

* **QR scan**: call `get_payments_by_qr_code` with the raw QR text. This handles both on-chain (when the QR includes `branta_id` / `branta_secret`) and lightning destinations.
* **Copy/paste**: call `get_payments` with the pasted text. Plain-text on-chain addresses will not return results in strict mode — they must be ZK-encoded. bolt11, Ark, and Silent Payments work as plain text (hash-ZK — no secret needed).

Always handle the `Result` and show nothing on not-found — a missing record just means the address was not posted to Branta.

```rust
use branta::{BrantaClientOptions, BrantaServerBaseUrl, BrantaService, PrivacyMode};

let service = BrantaService::new(BrantaClientOptions {
    base_url: BrantaServerBaseUrl::Production,
    privacy: PrivacyMode::Strict,
    default_api_key: None,
    hmac_secret: None,
});

async fn lookup(service: &BrantaService, input: &str, is_qr_code: bool) {
    let result = if is_qr_code {
        service.get_payments_by_qr_code(input, None).await
    } else {
        service.get_payments(input, None, None).await
    };

    match result {
        Ok(result) if result.payments.is_empty() => {
            // Not found — show nothing. The address may simply not exist in Branta.
        }
        Ok(result) => {
            // Render result.payments (name and logo)
        }
        Err(_) => {
            // Swallow errors — never surface a "not found" or lookup failure to the user.
        }
    }
}
```

{% endtab %}
{% endtabs %}

### Payment history

Once a payment resolves to a counterparty, render the counterparty name and logo in your transaction history, too.

**This is recommended for all wallet integrations:**

* **Persist at send time.** When the send-flow lookup returns a hit, store the counterparty `name` and `logo` on your transaction record. History rendering is then a local read — no extra network calls, and it works offline.

### Test your integration

Scan the [example QR codes](/setup/wallets/example-qr-codes) with your wallet to confirm each scenario renders the right thing before broadcast:

* **On-chain** and **Lightning** — counterparty name and logo render.
* **ZK On-chain** and **ZK Lightning** — encrypted destinations resolve via `branta_id` / `branta_secret`.
* **Not found** — your wallet shows nothing (a miss is not an error).
* **Payment history** — after paying a resolved QR, confirm the counterparty name and logo still render on the history row.

Each variant is published for both [Production](/setup/wallets/example-qr-codes/production) and [Staging](/setup/wallets/example-qr-codes/staging) — match the environment your SDK is pointed at.

### Wallets using Branta

See the live list at [branta.pro/network](https://branta.pro/network?tab=wallet).

To be listed, open a PR on [branta-network](https://github.com/BrantaOps/branta-network).


# Example QR Codes

Reference QR codes for testing wallet and scanner integrations.

Reference QR codes for [wallet developers](/setup/wallets) (and the [Branta scanner](https://scan.branta.pro/scan)) to confirm a Branta integration handles each destination type correctly — on-chain, Lightning, ZK variants, and not-found. Scan them with your wallet to verify counterparty info renders before broadcast.

Pages are split by [environment](/tech/environments):

* [**Production**](/setup/wallets/example-qr-codes/production) — points at `guardrail.branta.pro`.
* [**Staging**](/setup/wallets/example-qr-codes/staging) — points at `staging.guardrail.branta.pro`.

Each page exposes the QR variants as tabs:

* **On-chain** vs **Lightning** — the destination network.
* **ZK** vs non-ZK — [Zero-Knowledge](/tech/api/v2/adding-payments#zero-knowledge) destinations encrypt the payment destination so it never reaches Branta in plain text. On-chain ZK QRs carry `branta_id` + `branta_secret` query params alongside the address; Lightning ZK relies on the SDK encrypting the invoice itself before lookup. Non-ZK destinations are plain addresses or invoices.
* **ZK Testnet** (staging) — same as ZK on-chain, using a Bitcoin testnet (`tb1q…`) address.
* **Not found** — an unregistered destination. Per the [wallet integration pattern](/setup/wallets), wallets should render nothing on a miss.


# Production

Production environment.

Reference QR codes pointing at the **production** Branta environment (`guardrail.branta.pro`). See the [parent page](/setup/wallets/example-qr-codes) for what the four variants mean.

**Scanner:** <https://scan.branta.pro/scan?environment=production>

{% hint style="info" %}
Query param values in `bitcoin:` URIs are URI-encoded.
{% endhint %}

{% hint style="info" %}
**Strict privacy mode** (the wallet default) won't resolve the plain **On-chain** or plain **Lightning** tabs — strict skips plain on-chain, and the SDK auto-encrypts Lightning invoices so a plain-text-only record produces no match. Use the **ZK** tabs to exercise a successful lookup in strict, or switch your SDK to loose mode to test the plain tabs.
{% endhint %}

{% tabs %}
{% tab title="On-chain" %}
[Open verify page](https://guardrail.branta.pro/v2/verify/bc1qu3k6geqdjncaarsu2vq56tt8php5vsug9kasmq)

<figure><img src="/files/tLzpEOGgHAJuS6sIGymi" alt=""><figcaption><p><code>bitcoin:bc1qu3k6geqdjncaarsu2vq56tt8php5vsug9kasmq</code></p></figcaption></figure>
{% endtab %}

{% tab title="Lightning" %}
[Open verify page](https://guardrail.branta.pro/v2/verify/lnbc17760n1p4r4tqupp5yuapqmxldkc8smuwa6t8shkdg9gezulu0vc7htepfsvweph8kqfsdphgfexzmn5vysygetkv4kx7ur9wgsyc6t8dp6xu6twvusy27rpd4cxcegcqzzsxq97zvuqsp53564rg6w4xjqy7jamcfqxyy83a0j8nzfs0wpevs37t5ln49q6hrs9qxpqysgq47hpqmv34g25le8sceq9jdvul2nz7ucyu0vucv56nlfe40x7n3jsu8duxjrn6tgvdspt872crk9zeatafznm9c57m039z7wyx6g3njsqkchkdh)

<figure><img src="/files/0cVr1HSzc2ERLvLBNasP" alt=""><figcaption><p><code>lightning:lnbc17760n1p4r4tqupp5yuapqmxldkc8smuwa6t8shkdg9gezulu0vc7htepfsvweph8kqfsdphgfexzmn5vysygetkv4kx7ur9wgsyc6t8dp6xu6twvusy27rpd4cxcegcqzzsxq97zvuqsp53564rg6w4xjqy7jamcfqxyy83a0j8nzfs0wpevs37t5ln49q6hrs9qxpqysgq47hpqmv34g25le8sceq9jdvul2nz7ucyu0vucv56nlfe40x7n3jsu8duxjrn6tgvdspt872crk9zeatafznm9c57m039z7wyx6g3njsqkchkdh</code></p></figcaption></figure>
{% endtab %}

{% tab title="ZK On-chain" %}
[Open verify page](https://guardrail.branta.pro/v2/verify/z15b5EsbP5LHJrFco38%2BFp%2BHVaiopAY676NCKek8e1Q%2B4a370TyYhvloS8uLCUHfJ4CzeI%2FbOFmFDGpAQszB0gu1pJ1HOQ%3D%3D?simple=true#secret=c6e9eb30-6258-4432-9847-bdcc4fd4b0db)

<figure><img src="/files/3HOiUnxmzMzLmHEBPq8d" alt="" width="563"><figcaption><p><code>bitcoin:bc1q6745z6cy3u0k9nprurh3x804c4r7u3u8vxca2n?branta_id=z15b5EsbP5LHJrFco38%2BFp%2BHVaiopAY676NCKek8e1Q%2B4a370TyYhvloS8uLCUHfJ4CzeI%2FbOFmFDGpAQszB0gu1pJ1HOQ%3D%3D&#x26;branta_secret=c6e9eb30-6258-4432-9847-bdcc4fd4b0db</code></p></figcaption></figure>
{% endtab %}

{% tab title="ZK Lightning" %}
[Open verify page](https://guardrail.branta.pro/v2/verify/6x8c86cTKdFrl0bCX1DmJvEMtmQUjOKKasBHbvlhj%2FZ0zSp6KCMXrLWpMLfCrwYlDDeb%2Bj0KmxDam5wSXl2wtkkUAU0YZ4TuWWC9zQJ0RpCi1R1M%2Bamr2kJGPsoS5wRmJ4%2BwkQBnTdLpNEXT8BqySNnfsZOSjD3a%2FvsCO2EjKPp7Osekzl%2BpiwJowGyTuXnuBnpHCIEXcj7hVrcCYyXGVnnDCR5AxqTyj%2B3wVXBLIGpb33EUXrL69%2FaLjLgdHaCOdYkIbvQR0AkE7iEWeGezJVKRlfz9sxL3%2BcdpCZnfn%2Ffa1R9%2BEof7C6YZ0ItgSOCcyhS6rUKiDQNLqI0epgDjOi4sd5iQlW1fuKptwo5k2Dj2IYFd1rhnKf%2BPJOJ0r6bHL8fCDYh78bEYuhtczTvCu0XpDSITSrAeF9zvpintzxwLG2ufm4pZtHAY5YI90oSI940JMf1oFL0T8busTOZCTvYLni1Ihz9z4KePhWBqB6u%2Fjo47Lw%3D%3D#k-d94968=3DEFD9BAB71B35696C84E03A2E9A51F69640324BE589F8A7110F7E4A47DACF4D)

<figure><img src="/files/iRk05ZE6ukSYXfsOnrDy" alt=""><figcaption><p><code>lightning:lnbc17760n1p4r4flypp5k56kq3v2935rl3glkqu9vngfueud2zj87hjcff3t0kn0yrge0pfqdzjgfexzmn5vysz6gzyv4mx2mr0wpjhygzvd9nksarwd9hxwgz6v4ex7gztdehhwmr9v3nk2gz90psk6urvv5cqzzsxq97zvuqsp5hut3t0l0s5mvp9yr06v4253kqtf452z6c65s6g9sga445hc03v6s9qxpqysgqqm430zkk9uymjgvllr3aha88hc6q59etxasfqswn8r8pfm3dstlpp46azv906xtcj3wzprxup5fxn65a5wymt7zzq9sw9qdzx8rgdhcpk80nrg</code></p></figcaption></figure>
{% endtab %}

{% tab title="Not found" %}
An unregistered destination. Use this to verify your wallet's 404 path — Branta returns no record, and the wallet should render nothing rather than show an error or block the send.

[Open verify page](https://guardrail.branta.pro/v2/verify/bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4)

<figure><img src="/files/ErTex4N5iz9PjV1CXJfG" alt=""><figcaption><p><code>bitcoin:bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4</code></p></figcaption></figure>
{% endtab %}
{% endtabs %}


# Staging

Staging environment.

Reference QR codes pointing at the **staging** Branta environment (`staging.guardrail.branta.pro`). See the [parent page](/setup/wallets/example-qr-codes) for what the four variants mean.

**Scanner:** <https://scan.branta.pro/scan?environment=staging>

{% hint style="info" %}
Query param values in `bitcoin:` URIs are URI-encoded.
{% endhint %}

{% hint style="info" %}
**Strict privacy mode** (the wallet default) won't resolve the plain **On-chain** or plain **Lightning** tabs — strict skips plain on-chain, and the SDK auto-encrypts Lightning invoices so a plain-text-only record produces no match. Use the **ZK** tabs to exercise a successful lookup in strict, or switch your SDK to loose mode to test the plain tabs.
{% endhint %}

{% tabs %}
{% tab title="On-chain" %}
[Open verify page](https://staging.guardrail.branta.pro/v2/verify/bc1qgw3dzmhnyvcswc9r0v0z0ajtp8ulm4nuyeahwr)

<figure><img src="/files/PcKHboGdll9nq0KhE7CI" alt=""><figcaption><p><code>bitcoin:bc1qgw3dzmhnyvcswc9r0v0z0ajtp8ulm4nuyeahwr</code></p></figcaption></figure>
{% endtab %}

{% tab title="Lightning" %}
[Open verify page](https://staging.guardrail.branta.pro/v2/verify/lnbc25830n1p4quq9ppp5zszvpgxtu6uwyur6sf7rayc0meqprqlkv30xjzclh6nzm7gavd8sdzh2d6xzemfdenjqsnjv9h8gcfq95sygetkv4kx7ur9wgsyc6t8dp6xu6twvusy27rpd4cxcefq9pfhgct8d9hxw2gcqzzsxqzursp5fcfx5st7x8rgxra42j47hskmzkcz96mx84xcnvs9lpsmjyzqhw2q9qxpqysgq06lxdc93jjpuqsal9unlfct6wuv0v53yxa8kksl85g3qdw7qks7z9jkq39c6wgzar72luwd38sfj0klyqv0zgns4rq7nafnd8qeuudcqql7at4)

<figure><img src="/files/WiFPC4TBNXGSfW9fO5eu" alt=""><figcaption><p><code>lightning:lnbc25830n1p4quq9ppp5zszvpgxtu6uwyur6sf7rayc0meqprqlkv30xjzclh6nzm7gavd8sdzh2d6xzemfdenjqsnjv9h8gcfq95sygetkv4kx7ur9wgsyc6t8dp6xu6twvusy27rpd4cxcefq9pfhgct8d9hxw2gcqzzsxqzursp5fcfx5st7x8rgxra42j47hskmzkcz96mx84xcnvs9lpsmjyzqhw2q9qxpqysgq06lxdc93jjpuqsal9unlfct6wuv0v53yxa8kksl85g3qdw7qks7z9jkq39c6wgzar72luwd38sfj0klyqv0zgns4rq7nafnd8qeuudcqql7at4</code></p></figcaption></figure>
{% endtab %}

{% tab title="ZK On-chain" %}
[Open verify page](https://staging.guardrail.branta.pro/v2/verify/z15b5EsbP5LHJrFco38%2BFp%2BHVaiopAY676NCKek8e1Q%2B4a370TyYhvloS8uLCUHfJ4CzeI%2FbOFmFDGpAQszB0gu1pJ1HOQ%3D%3D?simple=true#secret=c6e9eb30-6258-4432-9847-bdcc4fd4b0db)

<figure><img src="/files/3HOiUnxmzMzLmHEBPq8d" alt="" width="563"><figcaption><p><code>bitcoin:bc1q6745z6cy3u0k9nprurh3x804c4r7u3u8vxca2n?branta_id=z15b5EsbP5LHJrFco38%2BFp%2BHVaiopAY676NCKek8e1Q%2B4a370TyYhvloS8uLCUHfJ4CzeI%2FbOFmFDGpAQszB0gu1pJ1HOQ%3D%3D&#x26;branta_secret=c6e9eb30-6258-4432-9847-bdcc4fd4b0db</code></p></figcaption></figure>
{% endtab %}

{% tab title="ZK Testnet" %}
[Open verify page](https://staging.guardrail.branta.pro/v2/verify/xJTcPmhJVFX0Ah2g13GgxPQSdmpHKGUw%2FFjCKBrkWUl86v0YlrtQ%2Fg4P22Jvk1OwaxjHMWyFMkbknMdktEopO8rArdBjRg%3D%3D?simple=true#secret=b1090ac9-ff89-4189-bdd0-fc9e56f3f794)

<figure><img src="/files/2vAedw3jjpYw3h81wQ7P" alt="" width="563"><figcaption><p><code>bitcoin:tb1qshra2mlujc9wfscvn4d8aqpyqgxzl55853xg7s?branta_id=xJTcPmhJVFX0Ah2g13GgxPQSdmpHKGUw%2FFjCKBrkWUl86v0YlrtQ%2Fg4P22Jvk1OwaxjHMWyFMkbknMdktEopO8rArdBjRg%3D%3D&#x26;branta_secret=b1090ac9-ff89-4189-bdd0-fc9e56f3f794</code></p></figcaption></figure>
{% endtab %}

{% tab title="ZK Lightning" %}
[Open verify page](https://staging.guardrail.branta.pro/v2/verify/egbaSEQCyGSq%2FDYaO%2BO3p66m%2F4JNERZiIowMNQr2WrjpoNif4qiIssT3opULTpg%2Fcj7LxUI8coUgSbGHza%2F4t0WzA%2FSp7p4DKTG97sgzwF4dSlP7q3p1hztmM8rksP0XALyzHyrgARVbQe0s4GRx9P4q2s8XuWkvFVaiTOW5xMJrPfDMC1sBwZvBs%2BnJtfuZZfLtdC31MJ5ychxqh%2FVYIqtkKcc0mBi5Uut8dWnowru4pCBFmK5g%2BXoY5i1cUcAVuWAlbs6yrgWJSceIYXv1IiCWaDTwSuBs1b2Qy7Ntt26MPlZxF0j3hPRr2rezUncseNAcpmiBJkRlJtwYgXOgt4RBBVXVt5A8mET3ItGOFHJz1RUB80ROd%2F%2B9oVwQ2YsQI33g26TIYIsqfXZ32HcU3sR%2Fir%2FWSqe81ZndH7CA%2Fz6oPCOaCdf0Hs54bQOORxEypjZ1mbOvQ25JdoAQHwC0sDxUCc4Z#k-c5d150=8BD0F0FD1E20372B98EE0442213C1375E73F522D254C6E395E98921023BDEE61)

<figure><img src="/files/ve4O5F7dhwBRlYuEI2I3" alt=""><figcaption><p><code>lightning:lnbc25840n1p4qml83pp5aztzddx4k87m0wkd6wmgxr9753400mcj7sa89sa392krmueqv9qqdz92d6xzemfdenjqsnjv9h8gcfq95s9xarpva5kueeqtf9jqsn0d36zqvf3ypzhsctdwpkx2cqzzsxqzursp5c6dt82gqpn5vucmqtctur0p3cuur6xqgc6348wtz7adtgug9uf2q9qxpqysgq5yt6x946w3664th4h02pug9yhgszpznqyfwzndjk2sxe0878slqkdhgce4mr5ky2ux4gy4yt0vsy536tencls8fvu5wdzyaq548yf4qqu0lyg7</code></p></figcaption></figure>
{% endtab %}

{% tab title="Not found" %}
An unregistered destination. Use this to verify your wallet's 404 path — Branta returns no record, and the wallet should render nothing rather than show an error or block the send.

[Open verify page](https://staging.guardrail.branta.pro/v2/verify/bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4)

<figure><img src="/files/ErTex4N5iz9PjV1CXJfG" alt=""><figcaption><p><code>bitcoin:bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4</code></p></figcaption></figure>
{% endtab %}
{% endtabs %}


# Platforms

Publish your payment destinations to Branta so senders see your name and logo before broadcast.

A **platform** is anything that issues a Bitcoin payment destination (on-chain address, Lightning invoice, etc.) to a sender — a merchant store, a payment processor, an invoicing tool, a donation page. Posting those destinations to Branta means senders see *who* they are paying before they hit send: your name, your logo, a verify link.

### What you get

* **Counterparty rendering** in any wallet using the Branta SDK and on [scan.branta.pro](https://scan.branta.pro/scan).
* **Public verify URL** for every payment (e.g. `guardrail.branta.pro/v2/verify/{destination}`) you can link from your own checkout.
* **Zero-knowledge mode**, where the destination is encrypted client-side before posting — Branta never sees the plain-text address.
* A listing in the [Branta network directory](https://branta.pro/network) if you open a PR on [branta-network](https://github.com/BrantaOps/branta-network).

### Onboarding

Four steps. Steps 1–3 happen once; step 4 is the integration work.

#### 1. Create an account

Sign up at [guardrail.branta.pro](https://guardrail.branta.pro). The Branta dashboard (we call it Guardrail) is where you manage your platform, request approval, and issue API keys.

For testing, the staging dashboard is at [staging.guardrail.branta.pro](https://staging.guardrail.branta.pro/session/new). Staging and production are siloed — see [Environments](/tech/environments).

#### 2. Submit a Platform Request

In Guardrail, create a Platform Request. A Branta admin reviews it (a light KYB check — we confirm your business and that the platform brand you're claiming is yours) and approves the platform on your account.

{% hint style="info" %}
Approvals are manual. If you need this turned around quickly for a launch or demo, mention the timeline in the request.
{% endhint %}

#### 3. Create an API key

Once your platform is approved, generate an API key from Guardrail. Keep it secret — see [Authentication](/tech/authentication) for header format and handling rules.

{% hint style="danger" %}
API keys carry sensitive privileges. Never commit them to source control, ship them in client-side code, or share them in plain text. Rotate immediately if exposed.
{% endhint %}

#### 4. Integrate

Pick the integration shape that matches what you already run:

| If you use…                                                                 | Do this                                                                                                                 |
| --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| **BTCPay Server**                                                           | Install the [BTCPay plugin](/setup/platforms/payment-gateway-options/btcpay-server) — no code.                          |
| **Zaprite**                                                                 | Connect Branta in the [Zaprite settings](/setup/platforms/payment-gateway-options/zaprite) — no code.                   |
| **Take My Sats**                                                            | Paste your API key into [Take My Sats store settings](/setup/platforms/payment-gateway-options/take-my-sats) — no code. |
| **Anything else** (your own checkout, a custom processor, a one-off script) | Use the [Custom Integration](/setup/platforms/custom-integration) — SDK or raw HTTP.                                    |

### After you're live

* **Test your integration.** Issue a real payment in your platform and confirm it resolves on [scan.branta.pro](https://scan.branta.pro/scan) and any compatible wallet. The [example QR codes](/setup/wallets/example-qr-codes) cover all four destination scenarios (on-chain, Lightning, ZK variants, not-found).

{% hint style="info" %}
If you publish ZK on-chain destinations (`isZk: true`), the QR code you render at checkout must include `branta_id` and `branta_secret` query parameters so wallets can look up and decrypt the record. See [QR code / payment URI](/setup/platforms/custom-integration#qr-code-payment-uri-zk-on-chain) in the Custom Integration guide and the [BIP-321 URI scheme spec](https://github.com/bitcoin/bips/blob/master/bip-0321.mediawiki).
{% endhint %}

\* \*\*Get listed.\*\* Open a PR on \[branta-network]\(<https://github.com/BrantaOps/branta-network>) to appear in the public \[partner directory]\(<https://branta.pro/network>). \* \*\*Hosting other platforms?\*\* See \[Parent Platforms]\(../parent-platforms.md) — you'll need the \`parent\_platform\` feature toggled on your account and a slightly different credential flow.


# Payment Gateway Options

Branta operates with the following no-code, platform options.

* [BTCPay Server](/setup/platforms/payment-gateway-options/btcpay-server)
* [Zaprite](/setup/platforms/payment-gateway-options/zaprite)
* [Take My Sats](/setup/platforms/payment-gateway-options/take-my-sats)


# BTCPay Server

[Official Plugin Link](https://plugin-builder.btcpayserver.org/public/plugins/branta): <https://plugin-builder.btcpayserver.org/public/plugins/branta>

{% embed url="<https://drive.google.com/file/d/1_EdWn0dtOxYfFZ4ZB0Ptaf2INf08FY8m/view?usp=share_link>" %}

## 1. Install Branta

[*Voltage*](https://www.voltage.cloud)*,* [*BTCPay Italia*](https://www.btcpayserver.it)*,* [*Bitcoin Aruba*](https://btc.aw) *and* [*pay.branta.pro*](https://pay.branta.pro/) *all have Branta installed already. If you run a hosted BTCPay Server and have Branta installed, open a* [*PR*](https://github.com/BrantaOps/branta-network) *to be added to our* [*Network Page*](https://branta.pro/network?tab=btcpay_server)*.*

* Login to BTCPayServer and click "Manage Plugins" on the lefthand side.

{% hint style="warning" %}
Note: If you don't see "Manage Plugins," you'll need an admin to do this for you.
{% endhint %}

* Search `Branta` and click `install`.

<figure><img src="/files/Aov76GiOBxAlicUv512K" alt=""><figcaption></figcaption></figure>

* Your BTCPay Server will restart.
* The Branta plugin appears.

<div align="center"><img src="/files/9sUfclluCmgfVtABknhA" alt=""></div>

## 2. Enter API Key

* Wait for the server to restart
* Under plugins, click `Branta`.
* Copy the API Key from your Branta Dashboard to the API Key settings field.
* Click `Save`.

## 3. Enable Branta

* Toggle Branta on by clicking <kbd>Enable Branta</kbd>.
* To confirm Branta is wired up, issue a test invoice and look for the `Click to verify with Branta` link in checkout — covered in step 4.

<figure><img src="/files/53wLUSQRqRxuSIqpHVJN" alt=""><figcaption></figcaption></figure>

## 4. Advanced settings (optional)

Expand the **Advanced** section on the settings page if you need to change defaults:

* **Enable Staging** + **Staging API Key** — point the plugin at `staging.guardrail.branta.pro` for testing. Use a staging-only API key.
* **Show Checkout Info on Verification Page** — include the invoice description on the Branta verify page.
* **Allow Guardrail Verification for** — TTL for each published destination. Options: 30 minutes (default), 4 hours, 1 day, 7 days.
* **Show Verify Link at Checkout** — toggle whether the `Click to verify with Branta` link appears on invoices.

## 5. Verify Branta is running

* Make a test invoice and you'll see `Click to verify with Branta`.
* You can also [QR scan](https://scan.branta.pro/scan) from your phone or compatible wallet

<figure><img src="/files/nZjFFCKkKi226dXGOvoM" alt=""><figcaption></figcaption></figure>


# Zaprite

## 1. Branta Connection

* Go to `Connections` within Zaprite and search for `Branta`.
* Click `Connect`.

<figure><img src="/files/Mol23RiR929EbJIuKO17" alt=""><figcaption></figcaption></figure>

## 2. Enter API Key

* Copy the API Key from your Branta Dashboard to the API Key settings field.
* `Check the box` to agree to Zaprite connecting to your Branta account.
* Click `Confirm Connection` to finalize.

<figure><img src="/files/i2nvEctLjfWtC2hUROiR" alt=""><figcaption></figcaption></figure>

## 3. Verify Branta is running

* [QR scan](https://scan.branta.pro/scan) from your phone or [compatible wallet](/setup/wallets#reference-wallets).


# Take My Sats

## 1. Create a Store

* Make an account on [Take My Sats](https://www.takemysats.com/)

## 2. Go to Store Settings

* Scroll down to `payment settings` inside the store settings page of Take My Sats
* Enable Branta payment verification via the toggle.&#x20;
  * Optional: Copy the API Key from your Branta Dashboard to the API Key settings field.
* Click `Save payment settings` to finalize.

<figure><img src="/files/IUpEY6le4kVh0BatS5Vf" alt=""><figcaption></figcaption></figure>

## 3. Verify Branta is running

* [QR scan](https://scan.branta.pro/scan) from your phone or [compatible wallet](/setup/wallets#reference-wallets).


# Custom Integration

POST your payment destinations to Branta from your own code — SDK or raw HTTP.

If none of the [no-code gateway options](/setup/platforms/payment-gateway-options) 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) — account, approved platform, and an API key.
* You know which [environment](/tech/environments) 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).

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).
* **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#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 %}

{% tab title="Python" %}
SDK: [`branta`](https://pypi.org/project/branta/) ([source](https://github.com/BrantaOps/branta-python))

```python
import os

from branta.enums import BrantaServerBaseUrl, DestinationType, PrivacyMode
from branta.options import BrantaClientOptions
from branta.v2 import BrantaService

service = BrantaService(BrantaClientOptions(
    base_url=BrantaServerBaseUrl.Production,
    default_api_key=os.environ["BRANTA_API_KEY"],
    privacy=PrivacyMode.Loose,
))

# Called whenever your platform issues a destination to a sender.
async def publish_destination(address: str):
    payment = (
        service.create_payment_builder()
        .add_destination(address, DestinationType.BitcoinAddress)
        .set_zk()  # omit .set_zk() to publish in plain
        .set_description("Invoice #12345")
        .add_metadata("order_id", "order-12345")
        .set_ttl(3600)
        .build()
    )

    result = await service.add_payment(payment)

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

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

{% tab title="Dart" %}
SDK: [`branta`](https://pub.dev/packages/branta) ([source](https://github.com/BrantaOps/branta-dart))

```dart
import 'dart:io' show Platform;

import 'package:branta/branta.dart';
import 'package:http/http.dart' as http;

final options = BrantaClientOptions(
  baseUrl: BrantaServerBaseUrl.production,
  defaultApiKey: Platform.environment['BRANTA_API_KEY']!,
  privacy: PrivacyMode.loose,
);
final brantaClient = BrantaClient(httpClient: http.Client(), defaultOptions: options);
final service = BrantaService(
  client: brantaClient,
  aesEncryption: AesEncryptionService(),
  defaultOptions: options,
);

// Called whenever your platform issues a destination to a sender.
Future<AddPaymentResult> publishDestination(String address) async {
  final payment = PaymentBuilder()
      .setDescription('Invoice #12345')
      .addDestination(address)
      .setZk() // omit .setZk() to publish in plain
      .setTtl(3600) // optional — seconds until Branta forgets this destination
      .addMetadata('order_id', 'order-12345') // optional — your own correlation id
      .build();

  final result = await service.addPaymentAsync(payment);

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

Privacy mode interacts with `setZk()` on destinations: 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="Kotlin" %}
SDK: [`pro.branta:branta`](https://central.sonatype.com/artifact/pro.branta/branta) ([source](https://github.com/BrantaOps/branta-kotlin))

```kotlin
val service = BrantaService(
    BrantaClientOptions(
        baseUrl = BrantaServerBaseUrl.Production,
        defaultApiKey = System.getenv("BRANTA_API_KEY"),
        privacy = PrivacyMode.Loose
    )
)

// Called whenever your platform issues a destination to a sender.
suspend fun publishDestination(address: String): AddPaymentResult {
    val payment = PaymentBuilder()
        .setDescription("Invoice #12345")
        .addDestination(address, DestinationType.BitcoinAddress)
        .setZk() // omit to publish in plain
        .setTtl(3600) // optional — seconds until Branta forgets this destination
        .addMetadata("order_id", "order-12345") // optional — your own correlation id
        .build()

    val result = service.addPayment(payment)

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

Privacy mode interacts with `.setZk()` on the `PaymentBuilder`: 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="Rust" %}
SDK: [`branta`](https://crates.io/crates/branta) ([source](https://github.com/BrantaOps/branta-rust))

```rust
use std::env;

use branta::{
    AddPaymentResult, BrantaClientOptions, BrantaError, BrantaServerBaseUrl, BrantaService,
    DestinationType, PaymentBuilder, PrivacyMode,
};

let service = BrantaService::new(BrantaClientOptions {
    base_url: BrantaServerBaseUrl::Production,
    default_api_key: Some(env::var("BRANTA_API_KEY").expect("BRANTA_API_KEY")),
    privacy: PrivacyMode::Loose,
    hmac_secret: None,
});

// Called whenever your platform issues a destination to a sender.
async fn publish_destination(
    service: &BrantaService,
    address: &str,
) -> Result<AddPaymentResult, BrantaError> {
    let payment = PaymentBuilder::new()
        .add_destination(address, Some(DestinationType::BitcoinAddress))
        .set_zk() // omit .set_zk() to publish in plain
        .set_description("Invoice #12345")
        .add_metadata("order_id", "order-12345") // optional — your own correlation id
        .set_ttl(3600) // optional — seconds until Branta forgets this destination
        .build();

    let result = service.add_payment(payment, None).await?;

    // For ZK: persist `result.secret` alongside your order — you need it to build
    // the `branta_secret` query param when rendering the payment QR / link.
    // `result.verify_url` is the public Branta verify page for this payment.
    Ok(result)
}
```

Privacy mode interacts with `.set_zk()` on the `PaymentBuilder`: in `Strict` mode (the wallet default) all destinations must be ZK or the call returns `Err`. `Strict` is also the SDK default — platforms typically set `privacy` explicitly to `Loose` and choose per-destination.
{% 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#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) reference. Authentication header rules: [Authentication](/tech/authentication).

### Surfacing Branta in your UI

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

#### 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) 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) — your wallet-side rendering should match.
* The SDKs expose `isApiKeyValid()` / `IsApiKeyValidAsync()` / `is_api_key_valid()` for a cheap credential health check.


# Parent Platforms

Publish payments on behalf of many merchants or brands, with a single shared key (recommended) or per-client keys.

A **parent platform** is a service that integrates Branta on behalf of many merchants or brands and `POST`s payments for each. There are two ways to structure this, depending on how you manage API keys:

* **Shared key (Recommended)** — you have a single Branta platform and a single API key covering every brand you serve. You tag each payment with the brand it belongs to at publish time, and Branta renders that brand's name/logo to senders.
* **Per-client keys** — each merchant has their own Branta platform and their own API key. You hold an HMAC secret and use it to sign every payment publish, proving the request actually originated from you and not from someone who got a copy of a merchant's API key. This is the original model and is still fully supported, but shared key is simpler for new integrations.

Pick shared key when the brands are internal to your own product and you're fine managing one API key for all of them — this covers most cases. Pick per-client keys only when each brand you serve needs its own independent Branta account (e.g. they onboard and manage their own API key).

{% hint style="info" %}
Single-tenant or self-hosted deployments like [BTCPay Server](/setup/platforms/payment-gateway-options/btcpay-server) are **not** parent platforms — each BTCPay store is its own Branta platform with its own API key. Parent Platforms apply when one piece of software is calling Branta on behalf of many brands.
{% endhint %}

## Shared key (Recommended)

You have a single Branta platform and a single API key covering every brand you serve — there's no per-merchant onboarding and no HMAC secret. Instead, you tag each payment with the brand it belongs to at publish time, and Branta renders that brand's name and logo to senders while still showing your platform as the hosting service.

### What you get

* **Per-brand counterparty rendering.** Each payment carries its own brand name and logo to senders, tagged per-request rather than per-account.
* **One API key for every brand.** No separate onboarding, no per-brand credentials to issue or rotate — you use your platform's normal API key.
* **Same privacy options as a regular platform** — plain or [Zero-Knowledge](/tech/api/v2/adding-payments#zero-knowledge), per destination.
* A listing in the [Branta network directory](https://branta.pro/network) once you open a PR on [branta-network](https://github.com/BrantaOps/branta-network).

### Onboarding

1. **Create an account.** Sign up at [guardrail.branta.pro](https://guardrail.branta.pro). For testing use the staging dashboard at [staging.guardrail.branta.pro](https://staging.guardrail.branta.pro/session/new).
2. **Submit a Platform Request.** In Guardrail, create a Platform Request like any other [Platform](/setup/platforms). A Branta admin reviews it (a light KYB check) and approves the platform on your account.
3. **Get shared-key access enabled.** A Branta admin needs to turn this on for your account — reach out and ask. There's no separate secret to generate: once enabled, your platform's existing API key can tag brands on payments.

### Integrate

`POST` payments to the normal endpoint (see [Adding Payments](/tech/api/v2/adding-payments)) using your platform's API key in `Authorization: Bearer …`, with a top-level `child_platform` object in the body naming the brand:

```json
{
  "description": "Order #4501 — Acme Coffee",
  "destinations": [
    {
      "value": "bc1qu3k6geqdjncaarsu2vq56tt8php5vsug9kasmq",
      "type": "bitcoin_address",
      "primary": true,
      "zk": false
    }
  ],
  "child_platform": {
    "name": "Acme Coffee",
    "logo_url": "https://example.com/acme-logo.png",
    "logo_light_url": "https://example.com/acme-logo-light.png"
  }
}
```

`child_platform.name` is required; `logo_url` and `logo_light_url` are both optional. On lookup, a payment with a tagged brand returns that brand as the primary `platform` / `platform_logo_url` fields, and your own platform's branding under `parent_platform` — wallets can optionally render that as a small secondary badge.

The SDKs handle this automatically — call `setChildPlatform(name, logoUrl, logoLightUrl)` (or the language equivalent) when building the payment. See [SDK](/tech/sdk) for the canonical reference; a minimal JS example:

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

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

const payment = new PaymentBuilder()
  .setDescription("Order #4501 — Acme Coffee")
  .addDestination("bc1qu3k6geqdjncaarsu2vq56tt8php5vsug9kasmq", "bitcoin_address")
  .setChildPlatform("Acme Coffee", "https://example.com/acme-logo.png")
  .build();

await service.addPayment(payment);
```

If you'd rather post directly, a raw `curl`:

```bash
curl -X POST https://guardrail.branta.pro/v2/payments \
  -H "Authorization: Bearer $BRANTA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "Order #4501 — Acme Coffee",
    "destinations": [
      {
        "value": "bc1qu3k6geqdjncaarsu2vq56tt8php5vsug9kasmq",
        "type": "bitcoin_address",
        "primary": true,
        "zk": false
      }
    ],
    "child_platform": {
      "name": "Acme Coffee",
      "logo_url": "https://example.com/acme-logo.png"
    }
  }'
```

For the full request/response schema (destination types, ZK fields, response shape) see the [Adding Payments](/tech/api/v2/adding-payments) reference.

{% hint style="info" %}
If you publish ZK on-chain destinations (`isZk: true`), the QR code you render at checkout must include `branta_id` and `branta_secret` query parameters so wallets can look up and decrypt the record. See [QR code / payment URI](/setup/platforms/custom-integration#qr-code-payment-uri-zk-on-chain) in the Custom Integration guide.
{% endhint %}

### After you're live

* **Test against staging first.** Use a staging API key from [staging.guardrail.branta.pro](https://staging.guardrail.branta.pro) and confirm tagged payments resolve on [scan.branta.pro](https://scan.branta.pro/scan) with the right brand name/logo.

## Per-client keys

The original parent-platform model, still fully supported for existing integrations — prefer [Shared key](#shared-key-recommended) for new ones. Each merchant has their own Branta platform and their own API key. The parent platform holds an HMAC secret and uses it to sign every payment publish, proving the request actually originated from it and not from someone who got a copy of a merchant's API key.

### How requests are authenticated

Every payment publish from a parent platform carries two credentials:

1. **The merchant's API key** in `Authorization: Bearer …`. This tells Branta which merchant's platform the payment belongs to, so the right name and logo render to senders.
2. **The parent's HMAC signature** in `X-HMAC-Signature` and `X-HMAC-Timestamp`. The merchant's API key is configured with a `parent_platform_id`, which tells Branta to validate the request's HMAC against the parent's active API secrets. Requests missing a valid signature are rejected.

The merchant onboards as a normal [Platform](/setup/platforms) and, when generating their API key in Guardrail, selects your parent platform from the dropdown. From then on, that key only works when accompanied by your HMAC signature.

### What you get

* **Per-merchant counterparty rendering.** Each merchant appears in the sender's wallet with their own name and logo, with your parent platform shown as the hosting service.
* **One HMAC secret, regardless of how many merchants you serve.** Each merchant has their own API key; you hold a single signing secret that authorizes your publishes on their behalf.
* **Same privacy options as a regular platform** — plain or [Zero-Knowledge](/tech/api/v2/adding-payments#zero-knowledge), per destination.
* A listing in the [Branta network directory](https://branta.pro/network) once you open a PR on [branta-network](https://github.com/BrantaOps/branta-network).

### Onboarding

#### 1. Create an account

Sign up at [guardrail.branta.pro](https://guardrail.branta.pro). For testing use the staging dashboard at [staging.guardrail.branta.pro](https://staging.guardrail.branta.pro/session/new). Staging and production are siloed — see [Environments](/tech/environments).

#### 2. Submit a Platform Request

In Guardrail, create a Platform Request. A Branta admin reviews it (a light KYB check) and approves the platform on your account.

#### 3. Get parent platform access enabled

A Branta admin needs to turn this on for your account. Reach out and ask — without it, the API Secrets section in Guardrail will not be available, and merchants won't be able to select you as a parent when issuing their API keys.

#### 4. Generate an API Secret

Once parent platform access is enabled, an **API Secrets** section appears in Guardrail. Generate a secret there.

The secret is shown **once at creation** — copy it immediately and store it server-side (your secret manager, not source control).

{% hint style="danger" %}
The API Secret is the HMAC signing material for every payment you publish on behalf of every merchant. Treat it like a root credential — store it server-side only, never ship it to clients, and rotate immediately if exposed.
{% endhint %}

You can hold multiple active secrets at the same time, which makes rotation safe: generate the new one, deploy it, then revoke the old one once nothing is signing with it.

#### 5. Integrate

For each merchant you serve:

1. They onboard as a normal [Platform](/setup/platforms) (steps 1–3 of that flow), then create an API key in their own Guardrail account and select your parent platform from the dropdown.
2. They share that API key with you (the same way they'd share an API key with any payment service they use).
3. Your service `POST`s their payment destinations to Branta using their API key in `Authorization`, signed with your HMAC secret.

The request body is identical to a regular platform publish (see [Adding Payments](/tech/api/v2/adding-payments)); the only difference is the two extra headers:

* `X-HMAC-Signature` — hex SHA-256 HMAC of the canonical message string, keyed by your API Secret.
* `X-HMAC-Timestamp` — Unix epoch seconds. Branta rejects requests outside a 30-minute window.

The canonical message is the request method, full URL, raw body, and timestamp joined with literal `|` characters, no whitespace:

```
POST|https://guardrail.branta.pro/v2/payments|{json-body}|1716998400
```

The SDKs handle this signing automatically when you supply the merchant's API key and your HMAC secret in their options. See [SDK](/tech/sdk) for the canonical reference (JavaScript, .NET, Python, Dart, Kotlin, Rust); a minimal JS example:

```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";

// One service instance per merchant — the API key changes per merchant,
// the HMAC secret is yours and stays constant.
const service = new BrantaService({
  baseUrl: BrantaServerBaseUrl.Production,
  defaultApiKey: merchant.brantaApiKey,         // merchant's key
  hmacSecret: process.env.BRANTA_API_SECRET!,   // your secret
  privacy: PrivacyMode.Loose,
});

await service.addPayment({
  description: "Order #4501 — Acme Coffee",
  destinations: [
    {
      value: "bc1qu3k6geqdjncaarsu2vq56tt8php5vsug9kasmq",
      type: DestinationType.BitcoinAddress,
      isPrimary: true,
      isZk: false,
    },
  ],
});
```

If you'd rather sign requests yourself, a raw `curl`:

```bash
BODY='{"description":"Order #4501","destinations":[{"value":"bc1q...","type":"bitcoin_address","primary":true,"zk":false}]}'
TS=$(date +%s)
MSG="POST|https://guardrail.branta.pro/v2/payments|${BODY}|${TS}"
SIG=$(printf '%s' "$MSG" | openssl dgst -sha256 -hmac "$BRANTA_API_SECRET" | awk '{print $2}')

curl -X POST https://guardrail.branta.pro/v2/payments \
  -H "Authorization: Bearer ${MERCHANT_API_KEY}" \
  -H "Content-Type: application/json" \
  -H "X-HMAC-Signature: ${SIG}" \
  -H "X-HMAC-Timestamp: ${TS}" \
  -d "$BODY"
```

For the full request/response schema (destination types, ZK fields, response shape) see the [Adding Payments](/tech/api/v2/adding-payments) reference.

{% hint style="info" %}
If you publish ZK on-chain destinations (`isZk: true`), the QR code you render at checkout must include `branta_id` and `branta_secret` query parameters so wallets can look up and decrypt the record. See [QR code / payment URI](/setup/platforms/custom-integration#qr-code-payment-uri-zk-on-chain) in the Custom Integration guide.
{% endhint %}

### After you're live

* **Test against staging first.** Use a staging API Secret from [staging.guardrail.branta.pro](https://staging.guardrail.branta.pro) and a staging merchant API key, and confirm payments resolve on [scan.branta.pro](https://scan.branta.pro/scan).
* **Rotate on exposure.** Generate a new secret, deploy it, then revoke the old one from Guardrail. Multiple active secrets can run side-by-side during the cutover.


# Tech


# Environments (Staging / Prod)

* **Staging** for testing:
  * [https://staging.guardrail.branta.pro](https://staging.guardrail.branta.pro/session/new)
* **Production** for live traffic:
  * <https://guardrail.branta.pro/>

#### Information:

* Each environment is siloed; no data is shared between staging and production.
* Any load testing, integration, or test-scenarios should be run on staging.


# SDKs

Official SDKs for integrating Branta. Each package README includes an Integration Guide covering send/receive side flows, display rules, and code examples.

## .NET

**NuGet:** `Branta` — [github.com/BrantaOps/branta-dotnet](https://github.com/BrantaOps/branta-dotnet)

**AI agent prompt:**

> Install the latest version of the Branta NuGet package. Find and read the README.md from the installed package, then follow the Integration Guide section to implement Branta.

## JavaScript / TypeScript

**npm:** `@branta-ops/branta` — [github.com/BrantaOps/branta-js](https://github.com/BrantaOps/branta-js)

**AI agent prompt:**

> Install the latest version of @branta-ops/branta from npm. Read the README.md from the installed package, then follow the Integration Guide section to implement Branta.

## Python

**PyPI:** `branta` — [github.com/BrantaOps/branta-python](https://github.com/BrantaOps/branta-python)

**AI agent prompt:**

> Install the latest version of the branta package from PyPI (`pip install branta`). Read the README.md from the installed package, then follow the Integration Guide section to implement Branta.

## Dart / Flutter

**pub.dev:** `branta` — [github.com/BrantaOps/branta-dart](https://github.com/BrantaOps/branta-dart)

**AI agent prompt:**

> Install the latest version of the branta package from pub.dev. Read the README.md from the installed package, then follow the Integration Guide section to implement Branta.

## Kotlin

**Maven Central:** `pro.branta:branta` — [github.com/BrantaOps/branta-kotlin](https://github.com/BrantaOps/branta-kotlin)

**AI agent prompt:**

> Install the latest version of pro.branta:branta from Maven Central. Read the README.md from the installed package, then follow the Integration Guide section to implement Branta.

## Rust

**crates.io:** `branta` — [github.com/BrantaOps/branta-rust](https://github.com/BrantaOps/branta-rust)

**AI agent prompt:**

> Install the latest version of the branta crate from crates.io (`cargo add branta`). Read the README.md from the installed package, then follow the Integration Guide section to implement Branta.


# API


# V2


# Adding Payments

## POST /payments

>

```json
{"openapi":"3.0.0","info":{"title":"Branta API","version":"2"},"servers":[{"url":"https://staging.guardrail.branta.pro/v2"}],"paths":{"/payments":{"post":{"parameters":[{"in":"header","name":"Authorization","required":true,"schema":{"type":"string"},"description":"`Authorization: Bearer` header with token is required. \n\nKeep your API key confidential."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentRequest"}}}},"responses":{"201":{"description":"Payment created successfully"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Unauthorized"}}}},"422":{"description":"Unprocessable Content","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnprocessableContent"}}}}}}}},"components":{"schemas":{"PaymentRequest":{"type":"object","properties":{"destinations":{"type":"array","items":{"type":"object","properties":{"value":{"type":"string","description":"The payable address."},"type":{"type":"string","enum":["bitcoin_address","bolt11","bolt12","ln_url","tether_address","ln_address","ark_address"],"description":"The type of the destination address."},"zk":{"type":"boolean","description":"If the provided value is encrypted or not. <b>Note</b>: value must be pre-encrypted with `AES-GCM` when this option is set to true."}},"required":["value"]}},"ttl":{"type":"integer","description":"Branta will remove the payment after ttl seconds."},"description":{"type":"string"},"metadata":{"type":"string","description":"Optional stringified JSON to show the user."}},"required":["destinations","ttl"]},"Unauthorized":{"type":"object","properties":{"error":{"type":"string"}}},"UnprocessableContent":{"type":"object","properties":{"destinations":{"type":"string"},"ttl":{"type":"string"}}}}}}
```

## Zero Knowledge

Zero Knowledge requires the API caller to encrypt the destination value before `POST`. Below is a javascript example of the encryption algorithm. Our [SDKs](/tech/sdk) wrap the encrypt/decrypt functionality for you, if desired, or can be used for example code.

```javascript
async function encrypt(value, secret) {
  const keyData = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(secret));
  const key = await crypto.subtle.importKey('raw', keyData, { name: 'AES-GCM' }, false, ['encrypt']);

  const iv = crypto.getRandomValues(new Uint8Array(12));
  const encrypted = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv: iv },
    key,
    new TextEncoder().encode(value)
  );

  const combined = new Uint8Array(iv.length + encrypted.byteLength);
  combined.set(iv, 0);
  combined.set(new Uint8Array(encrypted), iv.length);

  return btoa(String.fromCharCode(...combined));
}
```

```javascript
const address = '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa';
const secret = crypto.randomUUID();

await encrypt(address, secret);
// Result:
// LQdBBewrzglmPYwUVoSjBJihA/Br8o+T1ArXGLaAuh7yJiW2dClzSBSUbUH1zhPo1WUBtr7JaFQ7wkK7CG4=
```


# Getting Payments

## Get a payment

> Get details of a payment by address.

```json
{"openapi":"3.0.0","info":{"title":"Branta API","version":"2"},"servers":[{"url":"https://staging.guardrail.branta.pro/v2","description":"Staging is meant for pre-production workflows"}],"paths":{"/payments/{payment_string}":{"get":{"summary":"Get a payment","description":"Get details of a payment by address.","parameters":[{"name":"payment_string","in":"path","required":true,"description":"The unique identifier string of the payment.","schema":{"type":"string"}}],"responses":{"200":{"description":"Payment details retrieved successfully.","content":{"application/json":{"schema":{"type":"object","properties":{"destinations":{"type":"object","properties":{"value":{"type":"string"},"type":{"type":"string","enum":["bitcoin_address","bolt11","bolt12","ln_url","tether_address","ln_address","ark_address"],"description":"The type of the destination address."},"zk":{"type":"boolean"}}},"created_at":{"type":"string","format":"date-time"},"platform":{"type":"string"},"platform_logo_url":{"type":"string"},"ttl":{"type":"number","format":"integer"}}}}}},"404":{"description":"Payment not found."}}}}}}
```


# Health Check

Unauthenticated liveness probe — returns `200 OK` whenever the server is up.

* <https://staging.guardrail.branta.pro/up>
* <https://guardrail.branta.pro/up>

### API key health check

Validates an API key (Bearer token in `Authorization`). Returns `200 OK` for an active key, `401 Unauthorized` for a missing, revoked, or expired key. This is what the SDKs' `isApiKeyValid()` / `IsApiKeyValidAsync()` / `is_api_key_valid()` call under the hood.

* `GET https://staging.guardrail.branta.pro/v2/api-keys/health-check`
* `GET https://guardrail.branta.pro/v2/api-keys/health-check`


# Key Rotation

**If you believe your API Key has been compromised, revoke the key in your Branta Dashboard.**

Keys can be set to expire at any date, or never.

Branta will send automated email reminders leading up to the expiration date in the following intervals:

* 30 days before
* 7 days before
* 2 days before


# Authentication

`POST`ing to Branta requires API key authentication.

Include `Authorization: Bearer my_key_123` in the HTTP Header to authenticate.

{% hint style="danger" %}
API keys carry sensitive privileges so they should be kept secure at all times. Do not commit them to code or share them publicly.
{% endhint %}


# Design

Showcasing to your customers that your business is verified by Branta is important for building credibility with them that your business is secure.

We have prepared a set of steps and ideas for how you can easily display to your customers that you are partnered with Branta to ensure you have an additional layer of security.


# UI

URL, API, or QR Scan

Platforms may display Branta however best suits the *brand, device, and context.*

* Optional or Mandatory Links
* Consuming the Branta JSON API for custom workflows.
* Using the Branta QR Scanner
* Embedded Wallets

## Optional Verification

*Shown on same screen as the Checkout/Address/Invoice/QR.*

* Staging `https://staging.branta.pro/v2/verify/<address>`
* Production `https://guardrail.branta.pro/v2/verify/<address>`

The URLs are portable; no apps or login required.

{% hint style="info" %}
The above are web pages to let the user verify an address from any browser.
{% endhint %}

## Mandatory Verification

Require verification before paying. Programmable for custom thresholds:

* $1,000+
* $10,000+
* etc

## JSON API

* Staging `https://staging.guardrail.branta.pro/v2/payments/<address>`
* Production `https://guardrail.branta.pro/v2/payments/<address>`

```
{
  "destinations": {
    "value": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
    "zk": false
  },
  "created_at": "2025-09-02T12:34:56Z",
  "platform": "Example Platform",
  "platform_logo_url": "https://staging.branta.pro/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6NDYzLCJwdXIiOiJibG9iX2lkIn19--742d857e192086ec8fc32f0d1792048c27da0d95/IMG_8391.jpeg",
  "ttl": 86400
}
```

## Using the Branta QR Scanner

Branta supports QR scanning from any device with a camera.

* <https://scan.branta.pro/scan>


# Website Badges

Convey trust and status to your customers by including the Branta Badge in your website footer.

<figure><img src="/files/zwMZD8hsiaWZt3Wp3QUn" alt=""><figcaption></figcaption></figure>

SVG Badges are available to copy from <https://guardrail.branta.pro/account/badges>

<figure><img src="/files/rBzNMVymQ6MSUeyawCIi" alt=""><figcaption></figcaption></figure>


# Branta Assets

Branta SVGs are available on [Github](https://github.com/BrantaOps/assets/tree/main/svg).


