> 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/parent-platforms.md).

# Parent Platforms

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.md) 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.md#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.md). 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.md)) 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.md) 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.md) 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.md#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.md) 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.md#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.md).

#### 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.md) (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.md)); 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.md) for the canonical reference (JavaScript, .NET, Python, Dart, Kotlin); 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.md) 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.md#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.
