# Authenticatie en ondertekening van verzoeken

> Onderteken API-verzoeken met HMAC-SHA256 met behulp van je project UUID en API key.

Elk API-verzoek (behalve inkomende webhooks) moet je project UUID en een handtekening van het verzoek bevatten. De handtekening bewijst dat het verzoek van jou afkomstig is en dat niemand het onderweg heeft gewijzigd.

## API keys

2328.io gebruikt **twee keys** die hetzelfde ondertekeningsalgoritme delen, maar verschillende endpoints bestrijken:

| Key | Gebruikt voor |
|-----|---------------|
| **API key** | Betalingen, statische wallets, saldo, wisselkoersen en verificatie van betaling- / statische-wallet-webhooks |
| **Payout API key** | Alle `/v1/payout/*`-endpoints en verificatie van uitbetalingswebhooks |

Beide keys staan in je projectinstellingen op [2328.io](https://2328.io). De voorbeelden hieronder zeggen generiek "API key" — vervang door de juiste key voor het endpoint dat je aanroept.

> **INFO:** **Meng** de twee keys **nooit**: een uitbetalingsverzoek ondertekenen met de gewone API key (of een betalingsverzoek met de payout key) levert een handtekeningfout op.

## Vereiste headers

| Header | Type | Vereist | Beschrijving |
|--------|------|---------|--------------|
| `Content-Type` | string | ja | Altijd `application/json` |
| `project` | string | ja | Je project UUID |
| `sign` | string | ja | HMAC-SHA256-handtekening van het verzoek, berekend met je API key |
| `User-Agent` | string | ja | Identificeert je applicatie (bijv. `MyShop/1.4 (+https://myshop.example)`). Verzoeken zonder `User-Agent` kunnen worden geblokkeerd. |

## Hoe de handtekening werkt

Zie de handtekening als een vingerafdruk van de body van het verzoek. Hij wordt opgebouwd door:

1. De body te serialiseren naar JSON (compact — geen extra whitespace).
2. Die JSON te encoderen in base64. Deze stap normaliseert de input over talen heen — zodra het pure ASCII is, produceert elke taal dezelfde bytes voor HMAC.
3. **HMAC-SHA256** te berekenen over de base64-string met je API key, en het resultaat te converteren naar lowercase hex.

Voor **GET** en andere verzoeken zonder body onderteken je in plaats van de JSON een lege string.

> **INFO:** De handtekening van de lege string is constant voor een gegeven API key. Je kunt hem cachen als je veel GET-aanroepen doet.

## Implementaties

<CodeSnippet name="apiSign" langs="php,js,ts,python,go" />

### Verzoeken zonder body (GET)

Voor verzoeken zonder body (bijv. `GET /v1/payout/status/{uuid}`) onderteken je een lege string:

<CodeSnippet name="apiSignBodyless" langs="curl,php,js,ts,python,go" />

## Volledig voorbeeld van een verzoek

<CodeSnippet name="fullRequestExample" langs="curl,php,js,ts,python,go" />

> **DANGER:** **Stel je API key nooit bloot in client-side code.** Onderteken verzoeken op je backend. Een gelekte API key geeft iedereen volledige toegang tot je merchantaccount.

## Webhook-handtekeningen verifiëren

Wanneer 2328.io je een webhook stuurt, draait hetzelfde algoritme in omgekeerde richting:

1. Haal het `sign`-veld uit de payload.
2. Encodeer de overige velden als JSON (compact, geen whitespace).
3. Encodeer die string in base64.
4. Bereken `HMAC-SHA256` met de juiste key.
5. Vergelijk met de ontvangen `sign` met behulp van een **constant-time** vergelijking (`hash_equals`, `crypto.timingSafeEqual`, `hmac.compare_digest`, `subtle.ConstantTimeCompare`, `OpenSSL.fixed_length_secure_compare`).

De ondertekeningskey hangt af van de bron van de webhook:

| Webhook | Key voor verificatie |
|---------|----------------------|
| Betaling- / statische-wallet-webhooks (`/v1/payment`, `/v1/static-wallet`) | **API key** |
| Uitbetalingswebhooks (`/v1/payout`) | **Payout API key** |

> **WARNING:** **Veelvoorkomende verificatie-valkuilen.** Je JSON-encoder moet **exact dezelfde bytes** produceren als de verzender — anders verschilt de Base64 en zal de handtekening niet kloppen.

- **Go**: gebruik `json.NewEncoder` met `SetEscapeHTML(false)`. De standaard `json.Marshal` escapet `<`, `>`, `&` naar `<` en breekt de handtekening.
- **Python**: geef `ensure_ascii=False` mee aan `json.dumps`. Zonder dit worden niet-ASCII-tekens (cyrillisch, Chinees, …) ge-escaped naar `\uXXXX`.
- **Compacte JSON**: geen witruimte tussen velden (`separators=(",", ":")` in Python).
- **Veldvolgorde** (Go): een gewone `map[string]any` randomiseert sleutels bij her-encoderen. Gebruik `json.RawMessage`, een geordende struct of strip `sign` uit de ruwe bytes.

Als verificatie blijft falen, voer dan zelf `apiSign` uit op de payload — die moet dezelfde hex-string produceren als de ontvangen `sign`.

> **INFO:** **Een geldige handtekening voorkomt geen replays.** Het bewijst alleen dat de webhook van 2328.io komt — het belet een aanvaller niet om een *opgevangen* webhook later opnieuw te posten. Controleer altijd idempotentie via `uuid` (of `txid` voor statische wallets) voordat je geld bijschrijft. Weiger met HTTP `401` als de handtekening ontbreekt of fout is.

Volledige codevoorbeelden staan op **[Webhook Notifications](/docs/webhooks#verifying-the-signature)**. Retry-afhandeling en idempotentieregels staan in [Best practices](/docs/webhooks#best-practices).