> ## Documentation Index
> Fetch the complete documentation index at: https://docs.brand-wallet.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Register a customer

> Register a new or existing BrandWallet customer into a campaign, creating their wallet card and loyalty balances.

```http theme={null}
POST $BASE_URL/v1/customers/register
```

**Registers a customer into a campaign: opens a wallet card (with barcode) and initializes the campaign's loyalty balances. Pass `customer.id` to register an existing customer, or omit it to create the customer and register them in one step. The returned `cards[].id` is used directly as `card` on [POST /v1/transactions/process](/api/process-transaction).**

> **Auth:** `Authorization: Bearer <accessToken>` — an organization-scoped token from [Authentication](/api/authentication). Missing or invalid token returns **401**; an organization without an active subscription returns `invalid_subscription` (**403**).

Payload schema:

```json theme={null}
{
  "campaignId": "14c5a0ed-537e-400c-8463-e72bf277f600",  // required — the campaign to register into
  "customer": {
    "id": "9b2f6c1e-3d4a-4f8b-9c0d-7e5a1b2c3d4e",       // optional — registers this EXISTING customer; other fields are ignored
    "data": {                                            // required when id is omitted — same model as Create
      "phone": "+905551234567",
      "email": "jane@example.com",
      "fullname": "Jane Doe"
    },
    "metaData": { "source": "pos" },                     // optional
    "consents": { "marketing": true | false },           // optional
    "opaqueId": "ext-cust-10432"                         // optional
  }
}
```

### Body

| Field | Type | Description |
| - | - | - |
| `campaignId` | string | Required. The campaign to register the customer into (provided during onboarding, or from your campaign setup). Returns `invalid_campaign` (400) when no published campaign matches. |
| `customer` | object | Required. The customer to register — two modes: |
| `customer.id` | string | Optional. **Existing-customer mode:** registers this customer into the campaign; all other `customer` fields are ignored (the profile is not modified). Returns `customer_not_found` (400) when the id does not match a customer of your organization. |
| `customer.data` | object | Required when `id` is omitted. **New-customer mode:** same model and validations as [Create](/api/create-customer) — `invalid_phone`, `existing_customer_with_email`, `existing_customer_with_phone`, `opaque_id_already_exists` (all 400). |
| `customer.metaData` / `customer.consents` / `customer.opaqueId` | — | Optional in new-customer mode, same as Create; ignored in existing-customer mode. |

> Registering the same customer into the same campaign twice is **idempotent**: no error is returned, the customer's existing card comes back, and nothing changes. Registration never resets existing loyalty balances — only missing balances are initialized at 0.

### Example

```bash theme={null}
# Register an existing customer
curl --request POST \
  --url "$BASE_URL/v1/customers/register" \
  --header 'Authorization: Bearer <accessToken>' \
  --header 'Content-Type: application/json' \
  --data '{
    "campaignId": "14c5a0ed-537e-400c-8463-e72bf277f600",
    "customer": { "id": "9b2f6c1e-3d4a-4f8b-9c0d-7e5a1b2c3d4e" }
  }'
```

```bash theme={null}
# Create + register in one step
curl --request POST \
  --url "$BASE_URL/v1/customers/register" \
  --header 'Authorization: Bearer <accessToken>' \
  --header 'Content-Type: application/json' \
  --data '{
    "campaignId": "14c5a0ed-537e-400c-8463-e72bf277f600",
    "customer": {
      "data": { "phone": "+905551234567", "fullname": "Jane Doe" },
      "opaqueId": "ext-cust-10432"
    }
  }'
```

### Response

Returns **201** with the registered customer. `collectables` comes initialized with the campaign's loyalty balances; `cards` contains only the card of this registration (use [GET /v1/customers/find](/api/find-customer) for all of the customer's cards).

```json theme={null}
{
  "id": "9b2f6c1e-3d4a-4f8b-9c0d-7e5a1b2c3d4e",
  "data": {
    "phone": "+905551234567",
    "fullname": "Jane Doe"
  },
  "collectables": {
    "stamp_K3M9A": { "value": 0, "total": 0 }
  },
  "opaqueId": "ext-cust-10432",
  "status": 1,
  "createdAt": "2026-09-29T08:22:52.795Z",
  "updatedAt": "2026-09-29T08:23:05.079Z",
  "cards": [
    {
      "id": "6f1d2c88-9a41-4b7e-8d02-3c5a71e9f604",
      "barcode": "681738",
      "status": 1
    }
  ]
}
```

Notes:

* Optional fields that have no value are omitted from the response — you will not receive `null`. Exception: `opaqueId` is `null` when not assigned.
* In new-customer mode, `data.phone` is returned in its normalized (E.164) form.
* Every new card gets its own barcode — a customer's cards from different campaigns have different barcodes. `cards[].barcode` (a string) can be used to look the customer up at checkout via [GET /v1/customers/find](/api/find-customer).
* In new-customer mode, if a validation error occurs before the card is opened, the customer is not created either — no partial records are left behind.
