Entwickler/Erste Schritte
Authentifizierung
Jede API-Anfrage enthält Ihre Key-ID und eine Signatur. Die Signatur beweist, dass die Anfrage von Ihnen kommt, und niemand kann sie verändern oder wiederholen. Ihr Signing Secret selbst wird nie gesendet.
Siehe auch: Produkte und Bestellungen · Sicherheit · Übersicht
#Zugangsdaten
| Zugangsdaten | Beispiel | Geheim? |
|---|---|---|
| Key-ID | ck_live_... | Nein. Sie zeigt, welchen Key Sie verwenden. |
| Signing Secret | cs_live_... | Ja. Damit signieren Sie. Es verlässt nie Ihren Server. |
| Merchant ID | M00000001 | Nein. Bei API-Anfragen optional. |
- Ein Owner erstellt Keys im Portal (siehe API-Keys).
- Das Portal zeigt das Signing Secret einmal an, beim Erstellen des Keys. Niemand kann es erneut anzeigen, auch CardV nicht.
- Live-Keys beginnen mit
ck_live_undcs_live_. Sandbox-Keys beginnen mitck_test_undcs_test_. Ein Key aus der einen Umgebung funktioniert nie in der anderen. - Die API funktioniert erst, wenn CardV Ihr Unternehmen freigegeben hat.
Dann zeigt
GET/accountden Wert"api_access_enabled": true.
#Header
Senden Sie bei jeder Anfrage diese vier Header, auch bei GET-Anfragen:
X-Key-Id: ck_live_xxxxxxxxxxxxxxxxxxxxxxxx
X-Timestamp: 1790000000
X-Nonce: 0123456789abcdef0123456789abcdef
X-Signature: e6e45d09ed271d8d721701b560d56a6f5104fd8a52aaad5380166f432bba6e89X-Key-Idist die öffentliche ID Ihres Keys.X-Timestampist die aktuelle Unix-Zeit in Sekunden. Sie darf höchstens 5 Minuten (300 Sekunden) von der Uhr von CardV abweichen.X-Nonceist ein Zufallswert, den Sie nie zweimal verwenden. Nehmen Sie 32 zufällige Hex-Zeichen.X-Signaturewird unter Anfrage signieren erklärt. Sie muss in Hex-Kleinbuchstaben geschrieben sein.
Sie können zusätzlich X-Merchant-Id senden. Dann muss der Wert zum Key passen.
#Anfrage signieren
Wandeln Sie Ihren Anfrage-Body einmal in Bytes um. Genau diese Bytes signieren und senden Sie. Eine
GET-Anfrage hat einen leeren Body.Bilden Sie den Hash des Bodys:
BODY_HASH= SHA-256 des Bodys, in Hex-Kleinbuchstaben. Für einen leeren Body ist dase3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.Verbinden Sie diese fünf Zeilen mit einem Zeilenumbruch (
\n), ohne Zeilenumbruch am Ende:<METHOD> <PATH with query string> <X-Timestamp> <X-Nonce> <BODY_HASH>METHODsteht in Großbuchstaben, zum BeispielGEToderPOST. Der Pfad enthält den Query-String genau so, wie Sie ihn senden, zum Beispiel/api/v1/skus?limit=50.Signieren Sie den Text:
X-Signature= HMAC-SHA256 dieses Textes, mit Ihrem Signing Secret als Schlüssel. Schreiben Sie das Ergebnis in Hex-Kleinbuchstaben.
Die Anfragen zur Handy-Aufladung werden genauso signiert, mit ihrem eigenen Pfad, zum Beispiel /api/v1/recharge/orders.
Der häufigste Fehler: Man signiert eine Fassung des JSON und sendet eine andere.
{"a":1} und {"a": 1} sind zum Beispiel unterschiedliche Bytes.
Achten Sie darauf, dass Ihre HTTP-Bibliothek den Body nach dem Signieren nicht verändert und den Query-String nicht umsortiert.
Testvektoren. Prüfen Sie Ihren Code mit diesen Werten. Das Secret ist nicht echt.
Signing secret cs_test_TEST_ONLY_not_a_real_secret_0123456789abcdef
X-Timestamp 1790000000
X-Nonce 0123456789abcdef0123456789abcdefEine GET-Anfrage:
Method GET
Path /api/v1/skus?limit=50
BODY_HASH e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
X-Signature 58f0362c355a6624fff9f0b84d47591c570263908832294375b7bf27731628e5Eine POST-Anfrage an /api/v1/orders mit diesem Body (106 Bytes, eine Zeile, ohne Zeilenumbruch am Ende):
{"external_order_id":"TEST-0001","items":[{"sku_id":"S000001","quantity":1,"expected_unit_price":"9.25"}]}BODY_HASH 5814ffcdadfa9eb69348150f9fd6895c00b4c37cf6cd0202b83174354645023e
X-Signature e6e45d09ed271d8d721701b560d56a6f5104fd8a52aaad5380166f432bba6e89#Codebeispiele
Alle Beispiele liefern die Ergebnisse der Testvektoren. Laden Sie das Secret aus Ihrem Secret Store, nicht aus dem Code.
cURL (bash + OpenSSL)
BASE_URL="https://sandbox.cardv.net"
REQ_PATH="/api/v1/orders" # do not call this variable PATH
BODY='{"external_order_id":"SHOP-10001","items":'
BODY+='[{"sku_id":"S000001","quantity":1,"expected_unit_price":"9.25"}]}'
TS=$(date +%s)
NONCE=$(openssl rand -hex 16)
BODY_HASH=$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | sed 's/^.*= //')
SIG=$(printf 'POST\n%s\n%s\n%s\n%s' "$REQ_PATH" "$TS" "$NONCE" "$BODY_HASH" \
| openssl dgst -sha256 -hmac "$CARDV_SIGNING_SECRET" -hex | sed 's/^.*= //')
curl -sS -X POST "$BASE_URL$REQ_PATH" \
-H "Content-Type: application/json" \
-H "X-Key-Id: $CARDV_KEY_ID" \
-H "X-Timestamp: $TS" \
-H "X-Nonce: $NONCE" \
-H "X-Signature: $SIG" \
--data-raw "$BODY"Verwenden Sie --data-raw, nicht -d @file. -d entfernt Zeilenumbrüche, dann passen die gesendeten Bytes nicht mehr zur Signatur.
Für eine GET-Anfrage signieren Sie GET, den Pfad mit Query-String und den Hash des leeren Bodys, und senden keinen Body.
Python (requests)
import hashlib, hmac, json, os, secrets, time
import requests
BASE_URL = "https://sandbox.cardv.net"
KEY_ID = os.environ["CARDV_KEY_ID"]
SIGNING_SECRET = os.environ["CARDV_SIGNING_SECRET"]
def signed_headers(method: str, path: str, body: bytes) -> dict:
ts, nonce = str(int(time.time())), secrets.token_hex(16)
body_hash = hashlib.sha256(body).hexdigest()
text = "\n".join([method, path, ts, nonce, body_hash])
signature = hmac.new(SIGNING_SECRET.encode(), text.encode(), hashlib.sha256).hexdigest()
return {"X-Key-Id": KEY_ID, "X-Timestamp": ts, "X-Nonce": nonce, "X-Signature": signature}
def cardv_get(path: str) -> requests.Response:
# Put the query string in `path` so the signed path and the sent path are identical.
return requests.get(BASE_URL + path, headers=signed_headers("GET", path, b""), timeout=30)
def cardv_post(path: str, payload: dict) -> requests.Response:
body = json.dumps(payload, separators=(",", ":"), ensure_ascii=False).encode()
headers = {**signed_headers("POST", path, body), "Content-Type": "application/json"}
return requests.post(BASE_URL + path, data=body, headers=headers, timeout=30)
resp = cardv_post("/api/v1/orders", {
"external_order_id": "SHOP-10001",
"items": [{"sku_id": "S000001", "quantity": 1, "expected_unit_price": "9.25"}],
})
print(resp.status_code, resp.json())
order_id = resp.json()["order"]["order_id"]
print(cardv_get(f"/api/v1/orders/{order_id}").json()["status"])Node.js 18+ (eingebautes fetch)
import crypto from "node:crypto";
const BASE_URL = "https://sandbox.cardv.net";
const { CARDV_KEY_ID, CARDV_SIGNING_SECRET } = process.env;
function signedHeaders(method, path, body) {
const timestamp = String(Math.floor(Date.now() / 1000));
const nonce = crypto.randomBytes(16).toString("hex");
const bodyHash = crypto.createHash("sha256").update(body).digest("hex");
const text = [method, path, timestamp, nonce, bodyHash].join("\n");
const signature = crypto.createHmac("sha256", CARDV_SIGNING_SECRET).update(text, "utf8").digest("hex");
return { "X-Key-Id": CARDV_KEY_ID, "X-Timestamp": timestamp, "X-Nonce": nonce, "X-Signature": signature };
}
async function cardvGet(path) {
const res = await fetch(BASE_URL + path, { headers: signedHeaders("GET", path, Buffer.alloc(0)) });
return { status: res.status, body: await res.json() };
}
async function cardvPost(path, payload) {
const body = Buffer.from(JSON.stringify(payload), "utf8"); // serialize once
const headers = { ...signedHeaders("POST", path, body), "Content-Type": "application/json" };
const res = await fetch(BASE_URL + path, { method: "POST", headers, body });
return { status: res.status, body: await res.json() };
}
console.log(await cardvPost("/api/v1/orders", {
external_order_id: "SHOP-10001",
items: [{ sku_id: "S000001", quantity: 1, expected_unit_price: "9.25" }],
}));
console.log(await cardvGet("/api/v1/balance"));PHP (nur Signieren)
<?php
function cardv_sign(
string $secret, string $method, string $path, string $body, string $ts, string $nonce
): string {
$text = implode("\n", [$method, $path, $ts, $nonce, hash('sha256', $body)]);
return hash_hmac('sha256', $text, $secret); // lowercase hex
}
// Send exactly $body. For a GET call use $body = ''.
$body = json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$ts = (string) time();
$nonce = bin2hex(random_bytes(16));
$signature = cardv_sign($signingSecret, 'POST', '/api/v1/orders', $body, $ts, $nonce);
// Headers: X-Key-Id, X-Timestamp, X-Nonce, X-Signature#Fehler
Alle folgenden Fehler liefern HTTP 403 (nicht 401). Nur das Anfragelimit liefert 429. Der Body sieht so aus:
{"detail": "Invalid HMAC signature."}| Problem | Was tun |
|---|---|
| Key-ID unbekannt oder deaktiviert | Prüfen Sie X-Key-Id und die Umgebung (Live oder Sandbox). |
| API-Zugang nicht freigeschaltet | Warten Sie, bis CardV Ihr Unternehmen freigegeben hat. |
| Die IP Ihres Servers ist nicht erlaubt | Tragen Sie sie im Portal in die IP-Allowlist ein. |
| Endpunkt nur im Portal verfügbar | Nutzen Sie das Portal. Nur sechzehn Endpunkte funktionieren mit einem Key. |
| Signatur fehlt oder ist falsch | Korrigieren Sie Ihren Signaturcode. Testen Sie ihn mit den Testvektoren. |
| Zeitstempel zu alt oder zu neu | Synchronisieren Sie die Uhr Ihres Servers (NTP). |
| Nonce schon verwendet | Nehmen Sie für jede Anfrage eine neue zufällige Nonce. |
| Zu viele Anfragen (429) | Warten Sie die Sekunden aus Retry-After ab und versuchen Sie es dann erneut. |
Wichtig:
- Wenn Sie eine Anfrage erneut senden, erzeugen Sie einen neuen Zeitstempel, eine neue Nonce und eine neue Signatur. Der Body bleibt gleich. Siehe sicher erneut senden.
- Eine Nonce ist auch dann verbraucht, wenn die Signatur falsch war.
- CardV protokolliert fehlgeschlagene Versuche und alarmiert bei auffällig vielen sein Team.
#IP-Allowlist
Mit der IP-Allowlist können nur Ihre eigenen Server Ihren Key nutzen. Sie verwalten sie im Portal (Owner).
- Sie ist optional. Ohne Regeln werden Anfragen von jeder IP angenommen.
- Sobald es mindestens eine Regel gibt, erhalten Anfragen von anderen IPs HTTP 403.
- Regeln sehen so aus:
203.0.113.10/32(IPv4) oder2001:db8::/48(IPv6). - Sie gilt nur für Anfragen mit API-Key, nicht für die Anmeldung im Portal.
- Tragen Sie vor dem Einschalten alle Server-IPs ein, auch NAT-Gateways und Ausweich-Regionen.
#API-Keys
Ein Key kann genau die sechzehn Endpunkte aus Die API im Überblick nutzen. Dazu gehören Bestellen und Codes abrufen. Schützen Sie das Signing Secret daher wie ein Owner-Passwort.
Nur ein Owner kann Keys verwalten, im Portal unter Integrations → API keys. Das erledigen Sie für Live und Sandbox jeweils getrennt.
- Erstellen: Legen Sie einen Key an und geben Sie ihm einen Namen. Das Portal zeigt die Key-ID und das Signing Secret. Kopieren Sie das Secret sofort in Ihren Secret Store. Es wird nur dieses eine Mal angezeigt.
- Secret neu generieren: Wenn Sie das Signing Secret verlieren, generieren Sie es neu. Die Key-ID bleibt gleich. Das alte Secret funktioniert sofort nicht mehr. Verteilen Sie das neue Secret deshalb umgehend auf alle Ihre Server.
- Deaktivieren: Deaktivieren Sie einen Key, sobald Sie ihn nicht mehr brauchen. Das wirkt sofort und endgültig. Danach können Sie den deaktivierten Key aus der Liste entfernen. CardV bewahrt seinen Verlauf auf.
So wechseln Sie Keys ohne Ausfall:
- Erstellen Sie einen neuen Key und speichern Sie sein Secret.
- Verteilen Sie ihn auf alle Ihre Server.
- Prüfen Sie im Portal, dass der alte Key nicht mehr benutzt wird.
- Deaktivieren Sie den alten Key.
Wechseln Sie Keys mindestens einmal im Jahr und immer dann, wenn jemand mit Zugriff das Unternehmen verlässt. Wenn ein Key möglicherweise bekannt geworden ist, deaktivieren Sie ihn zuerst und untersuchen Sie den Fall danach.
#Alte Keys
Keys, die vor Oktober 2026 erstellt wurden, beginnen mit cvb2b_ und nutzen die älteren Header
X-Merchant-Id und X-Api-Key, mit einer Signatur nur bei POST-Anfragen.
Der alte Key wird bei jeder Anfrage mitgesendet und kann daher leichter bekannt werden.
Live und Sandbox akzeptieren diese Keys nicht mehr. Wechseln Sie jetzt zu einem neuen Key: Erstellen Sie einen im Portal, stellen Sie Ihren Code auf die oben genannten Header um und deaktivieren Sie dann den alten Key.
Fragen zur Integration? Schreiben Sie an [email protected] und nennen Sie Ihre Merchant ID sowie die Bestell- oder Request-ID.