Entwickler/Erste Schritte
Authentifizierung
Jede API-Anfrage enthält Ihre Merchant ID und Ihren API-Key.
Eine Bestellung (POST/orders) trägt zusätzlich eine Signatur. So kann niemand sie verändern oder wiederholen.
Siehe auch: Produkte und Bestellungen · Sicherheit · Übersicht
#Zugangsdaten
| Zugangsdaten | Beispiel | Geheim? |
|---|---|---|
| Merchant ID | M00000001 | Nein. Sie zeigt, wer Sie sind. |
| API-Key | cvb2b_... | Ja. Er ist Ihr Passwort und Ihr Signaturschlüssel. |
- Die Merchant ID erhalten Sie von CardV, sobald Ihr Konto freigegeben ist.
- Den API-Key erstellt ein Owner im Portal (siehe API-Keys).
- Live und Sandbox haben getrennte Keys. 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 zwei Header:
X-Merchant-Id: M00000001
X-Api-Key: cvb2b_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxBei POST/orders kommen diese drei hinzu:
X-Timestamp: 1790000000
X-Nonce: 0123456789abcdef0123456789abcdef
X-Signature: fd55fa31a3b6817d5368903dbb4bad4458381e024c17e688580bac96577acfedX-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 Bestellung signieren erklärt. Sie muss in Hex-Kleinbuchstaben geschrieben sein.
GET-Anfragen brauchen keine Signatur.
#Bestellung signieren
Wandeln Sie Ihre Bestellung einmal in JSON-Text um. Genau diese Bytes signieren und senden Sie.
Bilden Sie den Hash des Bodys:
BODY_HASH= SHA-256 des Bodys, in Hex-Kleinbuchstaben.Verbinden Sie diese fünf Zeilen mit einem Zeilenumbruch (
\n), ohne Zeilenumbruch am Ende:POST /api/v1/orders <X-Timestamp> <X-Nonce> <BODY_HASH>Signieren Sie den Text:
X-Signature= HMAC-SHA256 dieses Textes, mit Ihrem vollständigen API-Key als Schlüssel. Schreiben Sie das Ergebnis in Hex-Kleinbuchstaben.
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 mehr verändert.
Testvektor. Prüfen Sie Ihren Code mit diesen Werten. Der Key ist nicht echt.
API key cvb2b_TEST_ONLY_not_a_real_key_0123456789abcdef
Method POST
Path /api/v1/orders
X-Timestamp 1790000000
X-Nonce 0123456789abcdef0123456789abcdefBody (108 Bytes, eine Zeile, ohne Zeilenumbruch am Ende):
{"external_order_id":"TEST-0001","items":[{"sku_id":"S000001","quantity":1,"expected_unit_price":"9.2500"}]}Erwartete Ergebnisse:
BODY_HASH b0545ae25d54b219f27d8bd90e4dcbf26cf0d491f53982da67b8eef0a1a59960
X-Signature fd55fa31a3b6817d5368903dbb4bad4458381e024c17e688580bac96577acfed#Codebeispiele
Alle Beispiele liefern das Ergebnis des Testvektors. Laden Sie den Key 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.2500"}]}'
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_API_KEY" -hex | sed 's/^.*= //')
curl -sS -X POST "$BASE_URL$REQ_PATH" \
-H "Content-Type: application/json" \
-H "X-Merchant-Id: $CARDV_MERCHANT_ID" \
-H "X-Api-Key: $CARDV_API_KEY" \
-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.
Python (requests)
import hashlib, hmac, json, os, secrets, time
import requests
BASE_URL = "https://sandbox.cardv.net"
MERCHANT_ID = os.environ["CARDV_MERCHANT_ID"]
API_KEY = os.environ["CARDV_API_KEY"]
AUTH = {"X-Merchant-Id": MERCHANT_ID, "X-Api-Key": API_KEY}
def sign(path: str, body: bytes, ts: str, nonce: str) -> str:
body_hash = hashlib.sha256(body).hexdigest()
text = "\n".join(["POST", path, ts, nonce, body_hash])
return hmac.new(API_KEY.encode(), text.encode(), hashlib.sha256).hexdigest()
def cardv_get(path: str, params=None) -> requests.Response:
return requests.get(BASE_URL + path, params=params, headers=AUTH, timeout=30)
def cardv_post(path: str, payload: dict) -> requests.Response:
body = json.dumps(payload, separators=(",", ":"), ensure_ascii=False).encode()
ts, nonce = str(int(time.time())), secrets.token_hex(16)
headers = {
**AUTH,
"Content-Type": "application/json",
"X-Timestamp": ts,
"X-Nonce": nonce,
"X-Signature": sign(path, body, ts, nonce),
}
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.2500"}],
})
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_MERCHANT_ID, CARDV_API_KEY } = process.env;
const AUTH = { "X-Merchant-Id": CARDV_MERCHANT_ID, "X-Api-Key": CARDV_API_KEY };
function sign(path, body, timestamp, nonce) {
const bodyHash = crypto.createHash("sha256").update(body).digest("hex");
const text = ["POST", path, timestamp, nonce, bodyHash].join("\n");
return crypto.createHmac("sha256", CARDV_API_KEY).update(text, "utf8").digest("hex");
}
async function cardvPost(path, payload) {
const body = Buffer.from(JSON.stringify(payload), "utf8"); // serialize once
const timestamp = String(Math.floor(Date.now() / 1000));
const nonce = crypto.randomBytes(16).toString("hex");
const headers = {
...AUTH,
"Content-Type": "application/json",
"X-Timestamp": timestamp,
"X-Nonce": nonce,
"X-Signature": sign(path, body, timestamp, nonce),
};
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.2500" }],
}));PHP (nur Signieren)
<?php
function cardv_sign(
string $apiKey, string $path, string $body, string $ts, string $nonce
): string {
$text = implode("\n", ['POST', $path, $ts, $nonce, hash('sha256', $body)]);
return hash_hmac('sha256', $text, $apiKey); // lowercase hex
}
// Send exactly $body.
$body = json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$ts = (string) time();
$nonce = bin2hex(random_bytes(16));
$signature = cardv_sign($apiKey, '/api/v1/orders', $body, $ts, $nonce);#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 |
|---|---|
| Merchant ID oder API-Key fehlt oder ist falsch | Prüfen Sie beide Header und die Umgebung. |
| 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 sieben Endpunkte funktionieren mit einem Key. |
| Signatur fehlt oder ist falsch | Korrigieren Sie Ihren Signaturcode. Testen Sie ihn mit dem Testvektor. |
| Zeitstempel zu alt oder zu neu | Synchronisieren Sie die Uhr Ihres Servers (NTP). |
| Nonce schon verwendet | Nehmen Sie für jede Bestellanfrage 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 Bestellung 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 API-Key kann genau die sieben Endpunkte aus Die API im Überblick nutzen. Dazu gehören Bestellen und Codes abrufen. Schützen Sie ihn 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 den Key noch nicht an.
- Anzeigen (Reveal): CardV schickt Ihnen per E-Mail einen 6-stelligen Code. Er gilt 10 Minuten (5 Versuche). Geben Sie ihn ein, um den vollständigen Key zu sehen. Speichern Sie ihn sofort. Jedes Anzeigen wird protokolliert.
- Deaktivieren: Deaktivieren Sie einen Key, sobald Sie ihn nicht mehr brauchen. Das wirkt sofort und endgültig.
So wechseln Sie Keys ohne Ausfall:
- Erstellen Sie einen neuen Key und lassen Sie ihn anzeigen.
- 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.
Fragen zur Integration? Schreiben Sie an [email protected] und nennen Sie Ihre Merchant ID sowie die Bestell- oder Request-ID.