# Authentifizierung & Anfrage-Signierung

> Signieren Sie API-Anfragen mit HMAC-SHA256 unter Verwendung Ihrer Projekt-UUID und Ihres API-Schlüssels.

Jede API-Anfrage (mit Ausnahme eingehender Webhooks) muss Ihre Projekt-UUID und eine Anfrage-Signatur enthalten. Die Signatur belegt, dass die Anfrage von Ihnen stammt und unterwegs nicht verändert wurde.

## API-Schlüssel

2328.io verwendet **zwei Schlüssel**, die denselben Signieralgorithmus nutzen, aber unterschiedliche Endpoints abdecken:

| Schlüssel | Verwendet für |
|-----------|----------------|
| **API key** | Zahlungen, statische Wallets, Guthaben, Wechselkurse sowie Verifizierung von Zahlungs- und Static-Wallet-Webhooks |
| **Payout API key** | Alle `/v1/payout/*`-Endpoints und Verifizierung von Payout-Webhooks |

Beide Schlüssel finden Sie in den Projekteinstellungen unter [2328.io](https://2328.io). Die Beispiele unten sprechen generisch von "API key" — verwenden Sie jeweils den richtigen Schlüssel für den aufgerufenen Endpoint.

> **INFO:** **Vermischen Sie die beiden Schlüssel niemals**: Eine Auszahlungsanfrage mit dem regulären API-Schlüssel zu signieren (oder eine Zahlungsanfrage mit dem Payout-Schlüssel) führt zu einem Signaturfehler.

## Erforderliche Header

| Header | Typ | Pflicht | Beschreibung |
|--------|-----|---------|--------------|
| `Content-Type` | string | ja | Immer `application/json` |
| `project` | string | ja | Ihre Projekt-UUID |
| `sign` | string | ja | HMAC-SHA256-Signatur der Anfrage, berechnet mit Ihrem API-Schlüssel |
| `User-Agent` | string | ja | Identifiziert Ihre Anwendung (z. B. `MyShop/1.4 (+https://myshop.example)`). Anfragen ohne `User-Agent` können blockiert werden. |

## So funktioniert die Signatur

Stellen Sie sich die Signatur als Fingerabdruck des Anfrage-Bodys vor. Sie wird wie folgt erstellt:

1. Body in JSON serialisieren (kompakt — ohne zusätzliche Whitespaces).
2. Dieses JSON in base64 codieren. Dieser Schritt normalisiert die Eingabe sprachübergreifend — sobald es sich um reines ASCII handelt, erzeugt jede Sprache dieselben Bytes für HMAC.
3. **HMAC-SHA256** des Base64-Strings mit Ihrem API-Schlüssel berechnen und das Ergebnis in hex (Kleinbuchstaben) umwandeln.

Für **GET** und andere Anfragetypen ohne Body signieren Sie stattdessen einen leeren String anstelle des JSON.

> **INFO:** Die Signatur des leeren Strings ist für einen gegebenen API-Schlüssel konstant. Sie können sie zwischenspeichern, wenn Sie viele GET-Aufrufe ausführen.

## Implementierungen

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

### Anfragen ohne Body (GET)

Für Anfragen mit leerem Body (z. B. `GET /v1/payout/status/{uuid}`) signieren Sie einen leeren String:

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

## Vollständiges Anfragebeispiel

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

> **DANGER:** **Geben Sie Ihren API-Schlüssel niemals in clientseitigem Code preis.** Signieren Sie Anfragen auf Ihrem Backend. Ein durchgesickerter API-Schlüssel verschafft jedem vollen Zugriff auf Ihr Händlerkonto.

## Webhook-Signaturen verifizieren

Wenn 2328.io einen Webhook an Sie sendet, läuft derselbe Algorithmus rückwärts ab:

1. Ziehen Sie das Feld `sign` aus dem payload.
2. JSON-codieren Sie die übrigen Felder (kompakt, ohne Whitespace).
3. Codieren Sie diesen String in base64.
4. Berechnen Sie `HMAC-SHA256` mit dem passenden Schlüssel.
5. Vergleichen Sie das Ergebnis mit der erhaltenen `sign` mittels eines **konstantzeitigen** Vergleichs (`hash_equals`, `crypto.timingSafeEqual`, `hmac.compare_digest`, `subtle.ConstantTimeCompare`, `OpenSSL.fixed_length_secure_compare`).

Der Signierschlüssel hängt von der Webhook-Quelle ab:

| Webhook | Schlüssel zur Verifizierung |
|---------|------------------------------|
| Zahlungs- / Static-Wallet-Webhooks (`/v1/payment`, `/v1/static-wallet`) | **API key** |
| Payout-Webhooks (`/v1/payout`) | **Payout API key** |

> **WARNING:** **Häufige Verifizierungsfehler.** Ihr JSON-Encoder muss **exakt dieselben Bytes** erzeugen wie der Absender — andernfalls unterscheidet sich das Base64 und die Signatur passt nicht.

- **Go**: Verwenden Sie `json.NewEncoder` mit `SetEscapeHTML(false)`. Das Standard-`json.Marshal` escaped `<`, `>`, `&` zu `<` und zerstört die Signatur.
- **Python**: Übergeben Sie `ensure_ascii=False` an `json.dumps`. Ohne dies werden Nicht-ASCII-Zeichen (Kyrillisch, Chinesisch, …) zu `\uXXXX` escaped.
- **Kompaktes JSON**: keine Leerzeichen zwischen den Feldern (`separators=(",", ":")` in Python).
- **Feldreihenfolge** (Go): Ein einfaches `map[string]any` randomisiert die Schlüssel beim erneuten Kodieren. Verwenden Sie `json.RawMessage`, ein geordnetes struct oder entfernen Sie `sign` aus den Rohbytes.

Wenn die Verifizierung weiterhin fehlschlägt, führen Sie `apiSign` selbst auf dem Payload aus — es muss dieselbe Hex-Zeichenkette erzeugen wie das empfangene `sign`.

> **INFO:** **Eine gültige Signatur verhindert keine Replays.** Sie beweist nur, dass der Webhook von 2328.io stammt — sie verhindert nicht, dass ein Angreifer einen *abgefangenen* Webhook später erneut postet. Prüfen Sie immer die Idempotenz per `uuid` (oder `txid` bei statischen Wallets), bevor Sie Geldmittel gutschreiben. Lehnen Sie mit HTTP `401` ab, wenn die Signatur fehlt oder falsch ist.

Vollständige Codebeispiele finden Sie auf **[Webhook Notifications](/docs/webhooks#verifying-the-signature)**. Wiederholungsverhalten und Idempotenzregeln stehen in [Best Practices](/docs/webhooks#best-practices).