> ## Documentation Index
> Fetch the complete documentation index at: https://next-developers.opencard.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart — Zero to First Webhook

> End-to-end OpenCard EMS quickstart — register a sandbox account, sign a TPA, create an organization, onboard a card holder, and receive your first webhook.

This is the full path: register → TPA signed → org live → cardholder consented → webhook firing.

Grab coffee. ☕ Let's go.

***

## Step 0: Register (sandbox)

👉 [sandbox-api.opencard.io/register](https://sandbox-api.opencard.io/register)

You get an **Account** + admin user. Verify email. Log in.

Your `accountId` is in the dashboard URL or API responses.

***

## Step 1: Create an account\_client

```bash theme={null}
# Dashboard: Account → OAuth Clients → Create
# This is an account_client — machine-to-machine credential for your EMS integration.

# Then get a token:
curl -X POST https://sandbox-api.opencard.io/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -H "Accept: application/json" \
  -d "grant_type=client_credentials" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "scope=account-tpas-write account-tpas-read account-tpa-signatories-write organizations-write organizations-read card-holders-write webhooks-write webhooks-read public-records-read account-card-issuers-read account-card-issuers-write"
```

Save the `access_token`. All subsequent calls:

```
Authorization: Bearer {access_token}
Accept: application/json
```

<Note>
  Scopes are space-separated. Request only what you need. Full list in [Authentication](/getting-started/authentication). Always send `Accept: application/json` so error responses stay JSON (not an HTML login page).
</Note>

***

## Step 2: Enable a card issuer

```bash theme={null}
# List available issuers
curl https://sandbox-api.opencard.io/api/v1/application/cardissuers \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json"

# Attach one to your account (use the cardIssuerId from above)
curl -X POST https://sandbox-api.opencard.io/api/v1/application/accounts/$ACCOUNT_ID/cardissuers/1 \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json"
```

***

## Step 3: Lookup company (optional but smart)

Before creating a TPA, check the public registry for signing combinations:

```bash theme={null}
curl "https://sandbox-api.opencard.io/api/v1/application/accounts/$ACCOUNT_ID/publicrecords?country=SE&organization_number=5561234567" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json"
```

Response includes `signature_combinations` — who can legally sign for this company. You'll add those people as TPA signatories next.

***

## Step 4: Create TPA

TPA = Transaction Processing Authorization. Legal permission for transaction data to flow.

```bash theme={null}
curl -X POST https://sandbox-api.opencard.io/api/v1/application/accounts/$ACCOUNT_ID/tpas \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "card_issuer_id": 1,
    "name": "Acme AB",
    "country": "SE",
    "organization_number": "5561234567",
    "language": "sv"
  }'
```

**What happens server-side:**

1. Looks up company in the public registry (sandbox returns test data)
2. Overrides `name` with registry name when found
3. Creates the TPA record
4. Snapshots legal text from the latest `tpa` template in the requested language

**Org number formats:**

| Country | Format      | Example      |
| ------- | ----------- | ------------ |
| 🇸🇪 SE | 10 digits   | `5561234567` |
| 🇳🇴 NO | 9 digits    | `123456789`  |
| 🇩🇰 DK | 8 digits    | `12345678`   |
| 🇫🇮 FI | `XXXXXXX-X` | `1234567-8`  |

Response → save `tpaId`.

***

## Step 5: Add signatories → email goes out 📧

Each signatory = someone authorized to sign for the company.

```bash theme={null}
curl -X POST https://sandbox-api.opencard.io/api/v1/application/accounts/$ACCOUNT_ID/tpas/$TPA_ID/signatories \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "email": "ceo@acme.se",
    "name": "Anna Andersson"
  }'
```

**What happens:**

1. Server generates a 40-char `token` for this signatory
2. **Email is queued immediately** with subject: `Authorise TPA for Acme AB`
3. Email contains signing link:

```
https://sandbox-api.opencard.io/accounts/{accountId}/tpas/{tpaId}/sign/{token}
```

4. Signatory clicks link → sees TPA legal text → signs with eID

Repeat for each required signatory (check `signature_combinations` from step 3).

<Warning>
  Signatories can only be updated/deleted while `signed=false`. Once signed, they're locked.
</Warning>

Full signing details → [TPA Flow](/ems/tpa-flow) and [eID Signing](/ems/eid-signing).

***

## Step 6: Wait for TPA to be signed ✍️

When all signatories have signed, OpenCard verifies signing rights and marks the TPA as signed.

Then:

* Signed PDF generated and stored
* `tpa.signed` webhook fires (if subscribed)
* TPA status → `pending-activation` (waiting for TPA to be confirmed)
* Each signatory gets email with signed PDF attached

***

## Step 7: Create organization

```bash theme={null}
curl -X POST https://sandbox-api.opencard.io/api/v1/application/accounts/$ACCOUNT_ID/organizations \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "reference_id": "client_acme_001",
    "tpa_id": '"$TPA_ID"',
    "name": "Acme AB"
  }'
```

`reference_id` = **your** internal client ID. It comes back in every webhook.

Save `organizationId`.

***

## Step 8: Configure webhook 🔔

```bash theme={null}
curl -X POST https://sandbox-api.opencard.io/api/v1/application/accounts/$ACCOUNT_ID/organizations/$ORG_ID/webhooks \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "url": "https://your-app.com/webhooks/opencard",
    "enabled": true,
    "card_transaction_authorized": true,
    "card_transaction_cleared": true,
    "card_transaction_deleted": true,
    "card_holder_created": true,
    "card_holder_signed_pdpc": true,
    "receipt_fetched": true,
    "transaction_true_vat": true,
    "tpa_signed": true
  }'
```

**Challenge happens immediately.** OpenCard sends:

```
GET https://your-app.com/webhooks/opencard?challenge=RANDOM_STRING
Header: X-Event: challenge
```

Your endpoint must respond with header:

```
X-Verify-Token: HMAC-SHA256(challenge, webhook_secret)
```

Until challenge passes, `active=false` and no events are delivered.

Full webhook setup → [Webhook Setup](/ems/webhooks/setup)

***

## Step 9: Create card holders 👤

Two paths — pick one. Full guide → [Card Holder Onboarding](/ems/card-holders).

### Path A: Email + eID (new employee)

```bash theme={null}
curl -X POST https://sandbox-api.opencard.io/api/v1/application/accounts/$ACCOUNT_ID/organizations/$ORG_ID/cardholders \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "reference_id": "employee_john_42",
    "email": "john@acme.se",
    "language": "sv"
  }'
```

John gets email → signs PDPC with eID → `card_holder.identified` → transactions flow.

### Path B: `identity_id` (instant — person already in OpenCard)

```bash theme={null}
# 1. List identities on the TPA (people who already have cards)
curl "https://sandbox-api.opencard.io/api/v1/application/accounts/$ACCOUNT_ID/tpas/$TPA_ID/identities?is_card_holder=false" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json"

# 2. Create card holder with identity_id — transactions start immediately
curl -X POST https://sandbox-api.opencard.io/api/v1/application/accounts/$ACCOUNT_ID/organizations/$ORG_ID/cardholders \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "reference_id": "employee_anna_07",
    "identity_id": 123,
    "skip_pdpc_email": true
  }'
```

`card_holder.identified` fires instantly + retroactive transaction webhooks replay. No waiting for user action. ⚡

***

## Step 10: Receive your first transaction 🎉

Once the issuer delivers transaction states and the cardholder has signed PDPC:

```json theme={null}
// POST to your webhook URL
// Header: X-Event: card.transaction.authorized

{
  "id": "10",
  "state": "authorized",
  "receiptable": true,
  "card_issuer": "nordea",
  "original_amount": 109.38,
  "original_currency": "SEK",
  "purchase_merchant": "Coop Svängsta",
  "purchase_date": "2022-09-02",
  "card_funding": "credit",
  "card_holder": { "reference_id": "employee_john_42" },
  "organization": { "reference_id": "client_acme_001" }
}
```

**You did it.** From zero to live transaction data.

***

## What's next?

* [Transaction states](/ems/webhooks/transactions) — how to handle authorized vs cleared vs deleted
* [All webhook events](/ems/webhooks/events) — full payload reference
* [Receipt enrichment](/ems/receipts) — digital receipts + true VAT
