Zum Inhalt springen
Zum Inhalt springen

ENTWICKLER

API-Dokumentation zur Zahlungsannahme

Erstellen Sie eine Zahlung, leiten Sie Ihren Kunden zur Zahlungsseite weiter und erhalten Sie das Ergebnis per Webhook oder Abfrage.

  • Basis-URLhttps://www.cryptoavans.com/api/v3
  • API-Version3.0.0

Erste Schritte

Umgebung

Testen Sie, ohne echtes Geld zu bewegen.

Echte Zahlungen. Ein Live-Schlüssel ist erforderlich.

Erste Schritte

Vom neuen Konto zur ersten Live-Zahlung in vier Schritten.

  1. Schlüssel erhalten

    Legen Sie mit “Registrieren” ein Konto an oder melden Sie sich an, öffnen Sie “API-Schlüssel und Integration” und erzeugen Sie einen Sandbox-Schlüssel. Tragen Sie zuvor die IP Ihres Servers und eine Webhook-Adresse ein. Details: Schlüssel erhalten.

  2. Zahlung erstellen

    Senden Sie ein signiertes POST /api/v3/payments (es gibt Beispielcode) und leiten Sie den Kunden zur zurückgegebenen payment_url. Felder und Regeln stehen unter Zahlungen.

  3. Testen und Ergebnis empfangen

    Schließen Sie mit einer Testkarte eine Testzahlung ab und prüfen Sie sie unter “Sandbox-API-Aufträge”. Ihr Webhook erhält ein signiertes payment.paid (Webhooks); alternativ lesen Sie GET /api/v3/payments/{code}. Schließen Sie die Bestellung nur bei Status paid ab.

  4. Live gehen

    Erzeugen Sie nach Ihrer ersten Sandbox-Bestellung den Live-Schlüssel, tragen Sie die Live-IP-Liste und die Webhook-Adresse ein und arbeiten Sie die Go-live-Checkliste ab.

Konventionen

Basis-URL
https://www.cryptoavans.com/api/v3
Format
application/json · UTF-8
Beträge
Dezimalzeichenketten: Fiatbeträge haben 2 Nachkommastellen (zum Beispiel "250.00"). usdt_amount hat 6.
Währungen
USD EUR AED TRY
Zeitstempel
UTC · ISO 8601
Fehlersprache
Wird mit dem Header Accept-Language gewählt. Standard ist Türkisch.

Guthaben, Rückerstattungen, Auszahlungen, Streitfälle und Webhook-Zustellungen finden Sie auf der Seite Kontovorgänge.

Wenn Sie die Karte stattdessen in Ihrem eigenen Zahlungsformular erfassen möchten, lesen Sie Direct API (v4).

Schlüssel erhalten

Sandbox und Produktivbetrieb haben getrennte Schlüssel, IP-Listen und Webhook-Adressen. Beide Schlüssel können gleichzeitig aktiv sein.

  1. Öffnen Sie “API-Schlüssel und Integration”.

  2. Kopieren Sie die 6-stellige Kontonummer oben. Senden Sie sie bei signierten Anfragen als X-CAV-Account-No.

  3. Tragen Sie unter “Sandbox” die öffentliche Server-IP in “IP-Zulassungsliste” ein und klicken Sie auf “IP hinzufügen”. Geben Sie IPv4- oder IPv6-Adressen einzeln ein. CIDR-Bereiche werden nicht unterstützt.

  4. Geben Sie unter “HTTPS-Webhook-Adresse” eine öffentliche HTTPS-Adresse an. Sie ist erforderlich und wird beim Erstellen des Schlüssels gespeichert. Später ändern Sie sie über “Webhook speichern”, ohne den Schlüssel zu erneuern.

  5. Klicken Sie auf “Schlüssel erstellen”. Speichern Sie API-Schlüssel und geheimen Schlüssel, markieren Sie “Ich habe den geheimen Schlüssel sicher gespeichert.” und klicken Sie auf “Gespeichert, schließen”.

Der Live-Bereich wird freigeschaltet, sobald Sie mindestens eine Sandbox-Bestellung erstellt haben. Die Zahlung muss nicht abgeschlossen werden.

  1. Erstellen Sie mit Ihrem Sandbox-Schlüssel mindestens eine Bestellung (POST /api/v3/payments). Im Panel wird der Live-Bereich freigeschaltet.

  2. Tragen Sie im Live-Bereich die IP-Adressen Ihrer Produktionsserver unter “IP-Zulassungsliste” ein. Die Liste ist von der Sandbox-Liste getrennt. Solange sie leer ist, wird jede Live-Anfrage abgelehnt.

  3. Tragen Sie unter “HTTPS-Webhook-Adresse” Ihre HTTPS-Webhook-URL für Live ein. Sie wird im nächsten Schritt zusammen mit dem Schlüssel gespeichert.

  4. Klicken Sie auf “Schlüssel erstellen” und speichern Sie den geheimen Live-Schlüssel auf Ihrem Server.

  5. Hinterlegen Sie den produktiven API-Schlüssel und geheimen Schlüssel in Ihrer Integration. API-Basis-URL und Anfrageformat bleiben gleich.

IP-Zulassungsliste

Anfragen werden nur von IP-Adressen auf Ihrer Zulassungsliste angenommen. Eine Anfrage von einer anderen Adresse wird mit 403 (9000012) abgelehnt und nicht verarbeitet.

  • Die Liste ist Pflicht. Sandbox und Live führen getrennte Listen, und solange die Liste einer Umgebung leer ist, kann für sie kein Schlüssel erstellt werden.
  • Es können nur einzelne IPv4- oder IPv6-Adressen eingetragen werden; Bereiche (CIDR) gibt es nicht. Wenn Ihr Server sowohl über IPv4 als auch über IPv6 nach außen verbindet, tragen Sie beide ein.
  • Maßgeblich ist die öffentliche Adresse, von der aus Ihr Server nach außen verbindet. Private Adressen (10.x, 192.168.x) nützen nichts; hinter einem NAT oder Proxy tragen Sie dessen ausgehende Adresse ein.
  • Klicken Sie neben der Adresse auf “IP entfernen” und bestätigen Sie. Bevor Sie die letzte IP eines aktiven Schlüssels entfernen, fügen Sie eine andere Adresse hinzu oder widerrufen Sie den Schlüssel.
  • Änderungen gelten sofort. Sie müssen den Schlüssel nicht erneuern.

Tragen Sie jede Adresse ein, von der aus Sie testen, etwa einen Entwicklungsserver oder die ausgehende Adresse Ihres Büros.

Tragen Sie Produktionsserver in die Produktivliste ein. Die Sandbox-Liste gewährt keinen Zugriff für produktive Anfragen.

Schlüssel erneuern und widerrufen

Bei Verlust des geheimen Schlüssels verwenden Sie “Schlüssel erneuern”. Der alte Schlüssel dieser Umgebung wird sofort widerrufen. Die andere Umgebung behält ihren Schlüssel.

  • Jede Umgebung hat einen aktiven Schlüssel. “Schlüssel erneuern” erstellt ein neues Paar und widerruft das alte sofort. Mit dem alten Schlüssel signierte Anfragen schlagen mit 9000003 fehl.
  • “Schlüssel widerrufen” deaktiviert den Schlüssel, ohne einen neuen auszustellen. Anfragen werden sofort abgelehnt.
  • Das Secret eines widerrufenen Schlüssels signiert weiterhin Webhooks für damit erstellte Zahlungen. Bewahren Sie das zugehörige Secret sicher für diese Zahlungen auf. Geht es verloren oder wird es kompromittiert, prüfen Sie deren Status mit authentifizierten GET-Anfragen und Ihrem aktuellen Schlüssel; vertrauen Sie keinen Webhooks mit kompromittiertem Secret.

Authentifizierung

Signieren Sie authentifizierte Anfragen mit HMAC-SHA256 und Ihrem geheimen Schlüssel. Die Signatur umfasst Zeitstempel, Methode, Pfad und Body-Hash. GET /api/v3/service-status ist öffentlich und braucht keine Signatur.

Erforderliche Header

HeaderBeschreibung
X-CAV-Account-NoIhre 6-stellige Kontonummer.
X-CAV-Api-KeyDer API-Schlüssel der Umgebung, die Sie aufrufen (Sandbox oder Live).
X-CAV-TimestampAktuelle Unix-Zeit in Sekunden, 10 Stellen. Anfragen mit mehr als 300 Sekunden Abweichung werden abgelehnt.
X-CAV-SignatureHMAC-SHA256 der kanonischen Zeichenkette, 64 Hex-Zeichen in Kleinbuchstaben.
Idempotency-KeyFür schreibende Anfragen erforderlich. Verwenden Sie 8–128 Zeichen aus A-Z a-z 0-9 _ -. Eine UUID eignet sich.
Content-Typeapplication/json bei Anfragen mit Body.

Beachten Sie die Hinweise zur Aufbewahrung unter Schlüssel anfordern.

So signieren Sie eine Anfrage

  1. Serialisieren Sie den JSON-Body einmal und berechnen Sie sha256(body) als Hex in Kleinbuchstaben. Bei Anfragen ohne Body verwenden Sie sha256("").

  2. Bilden Sie die kanonische Zeichenkette aus timestamp, METHOD, path und dem Body-Hash, getrennt durch Zeilenumbrüche (\n, kein abschließender Umbruch). Der Pfad enthält keinen Query-String.

  3. Berechnen Sie HMAC-SHA256(secret, canonical) und kodieren Sie das Ergebnis als 64 Hex-Zeichen in Kleinbuchstaben.

  4. Senden Sie die vier X-CAV-*-Header (Kontonummer, API-Schlüssel, Zeitstempel, Signatur); fügen Sie bei Schreibanfragen Idempotency-Key hinzu.

Kanonische Signaturzeichenkette

timestamp + "\n" + METHOD + "\n" + path + "\n" + hex(sha256(body))
signature = hmac_sha256(secret, canonical)  // 64 lowercase hex characters

path ist der vollständige Pfad ohne Query-String (z. B. /api/v3/payments). Eine Anfrage ohne Body verwendet den Hash der leeren Zeichenkette (sha256("")).

Signieren Sie exakt die Bytes, die Sie senden: Serialisieren Sie das JSON einmal und verwenden Sie dieselbe Zeichenkette für den Hash und für den Request-Body.

Beispiel (mit Fantasiedaten)

Alle folgenden Werte sind erfunden: Kontonummer 123456, Secret sk_test_EXAMPLE. Verwenden Sie die Werte aus Ihrem eigenen Panel.

Anfrage-Body

{"amount":"250.00","currency_code":"USD","merchant_reference":"ORDER-2026-0001"}

sha256(body)

280db1ad2f6a457444b4838e9da76682edbd328a1b62f7b9b762852d9c4354f7

Kanonische Signaturzeichenkette

1790000000
POST
/api/v3/payments
280db1ad2f6a457444b4838e9da76682edbd328a1b62f7b9b762852d9c4354f7

X-CAV-Signature

3d3a95fcbda890bb424184c914ef3e2371c43602988cf3a698dc357b8074c5e9

Signierte Anfrage senden

Jedes Beispiel erstellt eine Zahlung. Die Zugangsdaten werden aus Umgebungsvariablen gelesen. Schreiben Sie das Secret nicht in den Code. Jeder Durchlauf verwendet eine neue merchant_reference.

#!/usr/bin/env bash
# export MERCHANT_ACCOUNT_NO=... MERCHANT_API_KEY=... MERCHANT_API_SECRET=...   (sandbox key)
# GET requests: METHOD=GET, REQ_PATH=/api/v3/payments/<code>, BODY='' (sha256 of the empty string), no Content-Type / --data-binary
set -euo pipefail

BASE_URL="https://www.cryptoavans.com"
METHOD="POST"
REQ_PATH="/api/v3/payments"
REFERENCE="ORDER-$(date +%s)-$(openssl rand -hex 4)"   # a new merchant_reference for every payment
BODY="{\"amount\":\"250.00\",\"currency_code\":\"USD\",\"merchant_reference\":\"$REFERENCE\",\"return_url\":\"http://localhost/return\"}"

TIMESTAMP="$(date +%s)"
BODY_SHA256="$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')"
CANONICAL="$(printf '%s\n%s\n%s\n%s' "$TIMESTAMP" "$METHOD" "${REQ_PATH%%\?*}" "$BODY_SHA256")"
# the secret travels in the environment, not on the command line
SIGNATURE="$(printf '%s' "$CANONICAL" | python3 -c 'import hashlib,hmac,os,sys; print(hmac.new(os.environ["MERCHANT_API_SECRET"].encode(), sys.stdin.buffer.read(), hashlib.sha256).hexdigest())')"

curl --silent --show-error --request "$METHOD" "$BASE_URL$REQ_PATH" \
  --header "X-CAV-Account-No: $MERCHANT_ACCOUNT_NO" \
  --header "X-CAV-Api-Key: $MERCHANT_API_KEY" \
  --header "X-CAV-Timestamp: $TIMESTAMP" \
  --header "X-CAV-Signature: $SIGNATURE" \
  --header "Idempotency-Key: $(openssl rand -hex 16)" \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  --data-binary "$BODY"
<?php
$config = [
    'base'    => 'https://www.cryptoavans.com',
    'account' => getenv('MERCHANT_ACCOUNT_NO'),
    'key'     => getenv('MERCHANT_API_KEY'),
    'secret'  => getenv('MERCHANT_API_SECRET'),
];

function signedRequest(array $config, string $method, string $path, ?array $payload = null, ?string $idempotencyKey = null): array
{
    $method = strtoupper($method);
    $signingPath = explode('?', $path, 2)[0];
    // serialise once: the same bytes are hashed and sent
    $body = $payload === null ? '' : json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
    $timestamp = (string) time();
    $canonical = implode("\n", [$timestamp, $method, $signingPath, hash('sha256', $body)]);
    $signature = hash_hmac('sha256', $canonical, $config['secret']);

    $headers = [
        'X-CAV-Account-No: ' . $config['account'],
        'X-CAV-Api-Key: ' . $config['key'],
        'X-CAV-Timestamp: ' . $timestamp,
        'X-CAV-Signature: ' . $signature,
        'Content-Type: application/json',
        'Accept: application/json',
    ];
    if ($idempotencyKey !== null) {
        $headers[] = 'Idempotency-Key: ' . $idempotencyKey;
    }

    $ch = curl_init($config['base'] . $path);
    curl_setopt_array($ch, [
        CURLOPT_CUSTOMREQUEST  => $method,
        CURLOPT_HTTPHEADER     => $headers,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 30,
    ]);
    if ($body !== '') {
        curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
    }
    $response = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    curl_close($ch);
    if ($response === false) {
        throw new RuntimeException('No response; retry the same operation and Idempotency-Key.');
    }

    return [$status, json_decode((string) $response, true)];
}

// a new merchant_reference for every payment: seconds plus a random suffix
[$status, $payment] = signedRequest(
    $config,
    'POST',
    '/api/v3/payments',
    ['amount' => '250.00', 'currency_code' => 'USD', 'return_url' => 'http://localhost/return', 'merchant_reference' => 'ORDER-' . time() . '-' . bin2hex(random_bytes(4))],
    bin2hex(random_bytes(16))
);
echo $status, ' ', json_encode($payment, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), PHP_EOL;

if (!in_array($status, [200, 201], true)) {
    exit(1); // the error code is not a payment code
}

// GET: no body, so the hash is sha256("")
[$status, $fetched] = signedRequest($config, 'GET', '/api/v3/payments/' . $payment['code']);
echo $status, ' ', $fetched['status'] ?? '', PHP_EOL;

Anfragen mit veraltetem Zeitstempel werden abgelehnt. Halten Sie Ihre Serveruhr daher per NTP synchron. Wiederholt fehlerhafte Signaturen von einer IP-Adresse führen zu einer vorübergehenden Sperre (429).

Anfrageregeln

Idempotenz

Senden Sie bei jeder Schreibanfrage den Header Idempotency-Key. Er macht Wiederholungen sicher: Wie oft Sie dieselbe Anfrage auch senden, die Operation wird nur einmal ausgeführt.

  • Eine Wiederholung mit demselben Schlüssel und Body liefert das ursprüngliche Ergebnis und erzeugt nie eine zweite Operation. Für den aktuellen Stand verwenden Sie GET.
  • Derselbe Schlüssel mit anderem Body liefert 409 mit Code 9000010; ein ungültiger Schlüssel liefert 422 mit Code 1000009.
  • Erzeugen Sie für jede neue Operation einen neuen Zufallsschlüssel (eine UUID genügt) und speichern Sie ihn bei Ihrer Bestellung, damit eine Wiederholung ihn wiederverwenden kann.

Ratenbegrenzung

Anfragen werden pro API-Schlüssel und pro IP-Adresse begrenzt. Bei 429 warten Sie und versuchen es später erneut.

  • Bei 429 warten Sie, falls vorhanden, die Zeit aus Retry-After ab und versuchen es dann mit exponentiellem Backoff und Jitter erneut.
  • Bei 503 versuchen Sie es später mit demselben Idempotency-Key und Body erneut. Beheben Sie andere 4xx-Fehler vor einer Wiederholung; einen neuen Schlüssel verwenden Sie nur für eine neue Operation.

Wird eine Anfrage abgelehnt, finden Sie den code unter Fehlercodes. Häufigste Ursachen: falsche Signatur (9000001), alter Zeitstempel (9000002), falsche Zugangsdaten (9000003) und eine IP, die nicht auf der Zulassungsliste steht (9000012).

Zahlungen

Erstellen Sie eine Zahlung und lesen Sie ihren Status.

Zahlungsstatus

FeldBeschreibung
createdDie Zahlung ist offen. Ihre Frist steht in expires_at; die Zahlungsseite ist 24 Stunden gültig.
paidDie Zahlung wurde eingezogen. Eine Benachrichtigung payment.paid wird gesendet.
expiredDie Zahlungsseite ist ohne bestätigte Zahlung abgelaufen.

Lesen Sie den aktuellen Status mit GET /api/v3/payments/{code}. Nach einer Ablehnung kann der Kunde unter derselben payment_url erneut zahlen, solange die Zahlung created ist. Ist sie expired, erstellen Sie mit neuer merchant_reference und neuem Idempotency-Key eine neue Zahlung.

Regeln

  • Der Betrag muss innerhalb der Zahlungslimits Ihres Kontos liegen. Das Limit wird nach der Umrechnung in US-Dollar geprüft; der Standard liegt bei 3.00 bis 1100.00 USD. Außerhalb davon liefert die API 422 mit Code 9000005. Für ein anderes Limit wenden Sie sich an CryptoAvans.
  • merchant_reference hat höchstens 64 Zeichen und ist pro Konto und Umgebung eindeutig. Selbst nach Ablauf einer Zahlung lässt sie sich nicht wiederverwenden (409, 9000007); senden Sie daher für jede neue Zahlung eine neue.
  • Leiten Sie den Kunden als normale Seite (Navigation der obersten Ebene) zur payment_url. Die Zahlungsseite lässt sich nicht in einem iframe anzeigen.
  • return_url ist optional. Nach Abschluss der Zahlung zeigt die Ergebnisseite eine Schaltfläche, die den Kunden dorthin führt; an die Query werden code, status und, falls gesendet, merchant_reference angehängt. Es gibt keine automatische Weiterleitung.
  • Unbekannte Anfragefelder werden ignoriert. Bestätigen Sie die Zahlung per signiertem Webhook oder serverseitigem GET; die Rückkehr des Kunden auf Ihre Seite dient nur der Information.

In der Sandbox öffnet payment_url dieselbe Zahlungsseite; zahlen Sie mit einer Testkarte aus der Testumgebung.

In der Live-Umgebung wird die Zahlung von einer echten Karte eingezogen und nach ihrem Wertstellungsdatum Ihrem Guthaben gutgeschrieben.

POST/api/v3/paymentsIdempotency-Key erforderlich

Zahlung erstellen

Die Antwort 201 enthält payment_url und expires_at. Leiten Sie den Kunden zur payment_url; die Zahlungsseite ist 24 Stunden gültig. Bei 409 / 9000039 wiederholen Sie dieselbe Anfrage mit demselben Idempotency-Key.

Anfrage-Body

FeldTypErforderlichBeschreibung
amountstringErforderlichPositiver Betrag als Dezimalzeichenkette mit fester Skala und 2 Nachkommastellen (z. B. "250.00"), in currency_code. Er muss innerhalb der Zahlungslimits Ihres Kontos liegen (in USD nach Umrechnung zum aktuellen Kurs geprüft; Ihre Grenzen sehen Sie auf der Panelseite API-Schlüssel und Integration); andernfalls liefert die Anfrage 422 9000005.
currency_codestringErforderlichAusgangswährung.
USDEURAEDTRY
merchant_referencestring | nullOptionalIhre eigene Bestellreferenz (höchstens 64 Zeichen). Eindeutig pro Konto und Umgebung: Eine Referenz lässt sich nicht wiederverwenden, nicht einmal für eine abgelaufene Zahlung (409 9000007). Optional.
maxLength 64
webhook_urlstring | nullOptionalhttps://-URL, die die signierten Benachrichtigungen (payment.paid, payment.refunded, payment.failed) empfängt. Der Hostname muss per DNS auf eine öffentliche Adresse aufgelöst werden (keine private oder reservierte IP, kein localhost); die URL darf höchstens 500 Zeichen lang sein und keinen Benutzernamen und kein Passwort enthalten. Optional: Ohne Angabe wird die im Panel für die Umgebung Ihres Signaturschlüssels gespeicherte Webhook-URL verwendet; ist keine gespeichert, wird keine Benachrichtigung gesendet.
maxLength 500
merchant_namestring | nullOptionalAuf dem Zahlungsbildschirm angezeigter Name. Optional; Standardwert ist Hesap <account number> (Ihre 6-stellige Kontonummer). Ein Wert mit mehr als 120 Zeichen wird gekürzt, nicht abgelehnt.
return_urlstring · uri | nullOptionalOptionale return_url (bis zu 2048 Zeichen). Live erfordert HTTPS; Sandbox akzeptiert auch HTTP auf localhost, 127.0.0.1 oder [::1]. Keine Benutzerinformationen, Fragmente, Steuerzeichen oder Leerzeichen. Internationale Hostnamen werden in ASCII umgewandelt. Ungültige URL: 422 / 9000038. Wenn die Zahlung endet, zeigt die Ergebnisseite eine Schaltfläche, die den Kunden zu dieser Adresse führt; an die Query werden code, status und, falls gesendet, merchant_reference angehängt. Es gibt keine automatische Weiterleitung. Ändern Sie sie bei Wiederholungen mit demselben Idempotency-Key nicht.
maxLength 2048

Anfrage-Body · Beispiel

{
  "amount": "10.00",
  "currency_code": "USD",
  "merchant_reference": "INV-104",
  "merchant_name": "Test Shop",
  "return_url": "https://shop.example.invalid/return?basket=104"
}

limits enthält currency_code, min_amount und max_amount (null: keine Obergrenze). Die Beträge unten sind Beispiele, nicht Ihre Limits.

HTTP 422 / 9000005

{
  "type": "https://www.cryptoavans.com/errors/9000005",
  "title": "Invalid amount",
  "status": 422,
  "code": "9000005",
  "correlation_id": "example",
  "detail": "Amount outside the account limits.",
  "limits": {
    "currency_code": "USD",
    "min_amount": "25.00",
    "max_amount": null
  }
}

Antwort · 201

Zahlung erstellt. Bei Wiederholung mit demselben Idempotency-Key wird stattdessen 200 zurückgegeben.

Payment

FeldTypBeschreibung
codestringKurzer Zahlungscode (4–16 Zeichen, Kleinbuchstaben).
merchant_referencestring | nullIhre Bestellreferenz, unverändert zurückgegeben.
statusstringLebenszyklusstatus der Zahlung: created, bis der Kunde bezahlt, danach paid; expired, wenn der Link unbezahlt abläuft.
createdpaidexpired
amountstringBruttobetrag als Dezimalzeichenkette mit fester Skala und 2 Nachkommastellen, in currency_code.
currency_codestringAusgangswährung dieser Zahlung; lesen Sie den zurückgegebenen Wert, statt die Beispielwährung anzunehmen.
USDEURAEDTRY
environmentstringUmgebung, zu der diese Zahlung gehört.
sandboxlive
payment_urlstringLeiten Sie den Kunden als normale Seite (Navigation der obersten Ebene) zur zurückgegebenen payment_url; erstellen oder ändern Sie die Adresse nicht selbst. Die Seite lässt sich nicht in einen iframe einbetten. An diese API werden keine Kartendaten gesendet.
created_atstring · date-timeErstellungszeitpunkt (UTC, ISO-8601).
expires_atstring · date-timeUTC. Die Zahlungsseite ist nach der Erstellung 24 Stunden gültig. Erstellen Sie nach Ablauf eine neue Zahlung; es gibt keinen Endpunkt zur Verlängerung. Bestätigen Sie die Zahlung mit GET /api/v3/payments/{code} oder einem signierten Webhook; die Rückkehr des Kunden auf Ihre Seite dient nur der Information.
paid_atstring · date-time | nullZeitpunkt der Zahlung oder null.
payment_url_kindstringImmer platform (unsere Zahlungsseite). Das Feld bleibt für bestehende Integrationen erhalten; provider_hosted wird nicht mehr zurückgegeben.
platformprovider_hosted
cardMerchantCardSummary | nullKarteninformationen zum erfolgreich bezahlten Versuch; bis zur Zahlung oder ohne verfügbare Angaben null.
failurePaymentFailureSummary | nullLetzter fehlgeschlagener Versuch bei unbezahlter Zahlung, sonst null. Nicht endgültig: Die Zahlung kann bis expires_at bezahlt werden. attempt_count zählt fehlgeschlagene Versuche einschließlich Wiederholungen.

Antwort · Beispiel

{
  "code": "a1b2c3d4",
  "merchant_reference": "INV-104",
  "status": "created",
  "amount": "10.00",
  "currency_code": "USD",
  "environment": "live",
  "payment_url": "https://pay.example.com/l/a1b2c3d4",
  "payment_url_kind": "platform",
  "created_at": "2026-10-05T12:00:00Z",
  "expires_at": "2026-10-06T12:00:00Z",
  "paid_at": null,
  "card": null,
  "failure": null
}

Weitere Antworten

  • 200Idempotente Wiederholung: Die ursprüngliche Zahlung wird zurückgegeben; es wird keine zweite Zahlung erstellt.
  • 422Beträge außerhalb der Kontogrenzen (9000005, auch unter 0.01 nach Umrechnung oder über der Kapazität) liefern limits in der Kontobasiswährung. Sandbox verwendet dieselbe Prüfung. Bei anderen Validierungsfehlern kann limits fehlen.

Mögliche Fehler

GET/api/v3/payments/{code}

Zahlungsstatus abrufen

Parameter

FeldTypErforderlichBeschreibung
codestring · pathErforderlichDer Zahlungscode.
pattern ^[a-z0-9]{4,16}$

Antwort · 200

Die Zahlung.

Objekt: Payment (Felder siehe Zahlung erstellen).

Antwort · Beispiel

{
  "code": "ab34cdef",
  "merchant_reference": "ORDER-2026-0001",
  "status": "created",
  "amount": "250.00",
  "currency_code": "USD",
  "environment": "live",
  "payment_url": "https://example.com/l/ab34cdef",
  "created_at": "2026-09-26T09:00:00.000Z",
  "expires_at": "2026-09-27T09:00:00.000Z",
  "paid_at": null,
  "payment_url_kind": "platform"
}

Mögliche Fehler

Fehlgeschlagene Versuche

Payment.card enthält Angaben zum erfolgreichen Versuch, failure.card zum letzten fehlgeschlagenen Versuch. Nicht verfügbare Werte können null sein. Der Karteninhabername ist ein personenbezogenes Datum: Beachten Sie Ihre gesetzlichen Pflichten. Vollständige Kartennummer und Ablaufdatum werden nie bereitgestellt.

payment.failed wird einmal beim ersten fehlgeschlagenen Versuch gesendet: Kartenablehnung, fehlgeschlagenes 3-D Secure oder Abbruch durch den Zahler. Die Zahlung bleibt created; bis expires_at sind neue Versuche möglich, später kann payment.paid eintreffen. Weitere Fehler aktualisieren Payment.failure ohne neues Ereignis. attempt_count zählt fehlgeschlagene Versuche; detail_code dient, falls verfügbar, der Information. Verwenden Sie die stabilen failure.code-Werte unten.

PaymentFailure

FeldTypErforderlichBeschreibung
detail_codestring | nullErforderlichDetaillierter Antwortcode des Zahlungsnetzwerks, zum Beispiel 058 oder 511; Sandbox-Codes mit Punkt werden weiterhin unterstützt. Keine Transaktion: timeout mit Detail null. Integrationen sollten anhand des stabilen Felds code entscheiden.
pattern ^[0-9]{3}(?:\.[0-9]{3})?$
cardMerchantCardSummary | nullErforderlichOptionale Karteninformationen aus dem bestätigten erfolgreichen oder fehlgeschlagenen Versuch. issuing_country verwendet ISO 3166-1 alpha-3. Keine vollständige Kartennummer, Ablaufdatum, Sicherheitscode oder dienstleistereigene Kennungen.
codestringErforderlichStabiler, sicherer Grund aus der letzten Zahlungstransaktion, andernfalls aus der Authentifizierung. 511/51: insufficient_funds; 058/004: authentication_failed; 022: timeout; 001/002: cancelled; 527/501: declined; 526: provider_error. Keine Transaktion oder erfolgreiche Authentifizierung ohne Zahlung: timeout. Unbekannte Fehler: declined. Keine Anbieternachricht und keine sensiblen Kartendaten.
declinedinsufficient_fundsauthentication_failedcard_invalidcancelledprovider_errorexpired_cardinvalid_cvvtimeout
occurred_atstring · date-timeErforderlichAbschlusszeit des fehlgeschlagenen Versuchs in UTC mit Millisekunden.

Webhooks

Signierte JSON-Benachrichtigungen umfassen payment.paid, payment.failed und payment.refunded. Verarbeiten Sie nur bekannte Ereignistypen und ignorieren Sie unbekannte; ein fehlgeschlagener Versuch ist kein endgültiges Zahlungsergebnis.

Jede Benachrichtigung prüfen

  1. Lesen Sie den rohen Body vor dem Parsen von JSON. Lehnen Sie fehlende oder falsch formatierte Signaturen und Zeitstempel ab. Die Beispiele lehnen Zeitstempel mit mehr als 300 Sekunden Abweichung von Ihrer Serveruhr ab; halten Sie diese synchronisiert.

  2. Bilden Sie die kanonische Zeichenkette neu aus X-CAV-Timestamp, POST, dem Pfad Ihrer Webhook-URL (ohne Query-String) und dem sha256 des rohen Bodys.

  3. Berechnen Sie HMAC-SHA256 mit Ihrem geheimen Schlüssel und vergleichen Sie das Ergebnis zeitkonstant mit X-CAV-Signature. Verwenden Sie das Secret des Schlüssels, der die Zahlung erstellt hat, auch wenn Sie Ihren Schlüssel inzwischen erneuert haben.

  4. Gleichen Sie nach der Signaturprüfung das signierte environment, Zahlungscode, Betrag und Währung mit Ihrer Bestellung ab. Unterscheiden Sie payment.paid / payment.failed und payment.refunded anhand von event_type im signierten Body; eine Rückerstattung darf keine Auslieferung auslösen.

  5. Deduplizieren Sie atomar anhand von event_id im verifizierten Body. Vergleichen Sie den Wert ohne Beachtung der Großschreibung mit X-CAV-Delivery und lehnen Sie Abweichungen ab; der Header selbst ist nicht signiert. Verlassen Sie sich nicht auf die Zustellreihenfolge; prüfen Sie unerwartete Ereignisse über die Status-Endpunkte.

  6. Antworten Sie schnell mit 2xx, nachdem Sie das verifizierte Ereignis dauerhaft gespeichert oder vollständig verarbeitet haben. Schlägt das Speichern fehl, antworten Sie mit einem Fehler, damit erneut zugestellt werden kann. Antworten Sie bei bereits gespeicherten Duplikaten mit 2xx. Der Antwort-Body wird ignoriert, Weiterleitungen werden nicht verfolgt.

Verwenden Sie ein gültiges TLS-Zertifikat einer öffentlichen Zertifizierungsstelle; die Zertifikatsprüfung lässt sich nicht abschalten.

Beispiele zur Signaturprüfung

<?php
// $path is the path of YOUR webhook URL (no query string), e.g. /payment-webhook
function isValidWebhook(string $rawBody, array $headers, string $path, string $secret): bool
{
    $headers = array_change_key_case($headers, CASE_LOWER);
    $timestamp = $headers['x-cav-timestamp'] ?? '';
    $signature = $headers['x-cav-signature'] ?? '';
    if (!is_string($timestamp) || !is_string($signature)
        || preg_match('/^[0-9]{10}$/D', $timestamp) !== 1
        || preg_match('/^[0-9a-f]{64}$/D', $signature) !== 1
        || abs(time() - (int) $timestamp) > 300) {
        return false;
    }
    $canonical = implode("\n", [$timestamp, 'POST', $path, hash('sha256', $rawBody)]);

    return hash_equals(hash_hmac('sha256', $canonical, $secret), $signature);
}

$rawBody = file_get_contents('php://input');
$headers = [
    'X-CAV-Timestamp' => $_SERVER['HTTP_X_CAV_TIMESTAMP'] ?? '',
    'X-CAV-Signature' => $_SERVER['HTTP_X_CAV_SIGNATURE'] ?? '',
];
if (!isValidWebhook($rawBody, $headers, '/payment-webhook', getenv('MERCHANT_API_SECRET'))) {
    http_response_code(401);
    exit;
}

// After verification: compare event_id from the signed body with X-CAV-Delivery (case-insensitive),
// save the event once (deduplicate by event_id), then answer 2xx. On a save error answer 5xx so it is retried.
$event = json_decode($rawBody, true);
http_response_code(204);
const crypto = require("node:crypto");

// path is the path of YOUR webhook URL (no query string), e.g. /payment-webhook
function isValidWebhook(rawBody, headers, path, secret) {
  const timestamp = headers["x-cav-timestamp"] ?? "";
  const signature = headers["x-cav-signature"] ?? "";
  if (typeof timestamp !== "string" || typeof signature !== "string"
      || !/^[0-9]{10}$/.test(timestamp) || !/^[0-9a-f]{64}$/.test(signature)
      || Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp)) > 300) return false;
  const bodyHash = crypto.createHash("sha256").update(rawBody).digest("hex");
  const canonical = [timestamp, "POST", path, bodyHash].join("\n");
  const expected = crypto.createHmac("sha256", secret).update(canonical).digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(signature);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// rawBody must be the untouched request bytes (Buffer), not re-serialised JSON.
// After verification, deduplicate using event_id from the signed body; check X-CAV-Delivery matches.
// Check event_type and order fields, then durably save before 2xx (see the verification steps).
module.exports = { isValidWebhook };
import hashlib
import hmac
import re
import time


def is_valid_webhook(raw_body: bytes, headers: dict, path: str, secret: str) -> bool:
    """path is the path of YOUR webhook URL (no query string), e.g. /payment-webhook."""
    headers = {key.lower(): value for key, value in headers.items()}
    timestamp = headers.get("x-cav-timestamp", "")
    signature = headers.get("x-cav-signature", "")
    if (not isinstance(timestamp, str) or not isinstance(signature, str)
            or not re.fullmatch(r"[0-9]{10}", timestamp)
            or not re.fullmatch(r"[0-9a-f]{64}", signature)
            or abs(int(time.time()) - int(timestamp)) > 300):
        return False
    canonical = "\n".join([timestamp, "POST", path, hashlib.sha256(raw_body).hexdigest()])
    expected = hmac.new(secret.encode(), canonical.encode(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)


# After verification, deduplicate by event_id from the signed body; check X-CAV-Delivery matches.
# Check event_type and order fields, then durably save before 2xx (see the verification steps).

Wiederholungen

Jede Antwort außer 2xx, ein Timeout (etwa 10 Sekunden) oder ein Verbindungsfehler zählt als fehlgeschlagener Versuch. Es werden höchstens 6 Versuche unternommen; zwischen den Versuchen werden mindestens die unten angegebenen Wartezeiten eingehalten. Nach dem letzten fehlgeschlagenen Versuch wird die Zustellung als failed markiert; Sie können sie mit POST /api/v3/webhooks/deliveries/{id}/retry erneut einreihen.

Der Body und X-CAV-Delivery sind bei jedem Versuch gleich; X-CAV-Timestamp und X-CAV-Signature werden jedes Mal neu berechnet. Die id der Zustellung, der Header X-CAV-Delivery und event_id im Body sind dieselbe UUID in Großbuchstaben; vergleichen Sie sie ohne Beachtung der Groß- und Kleinschreibung.

VersuchWartezeit davor
1—
260 s (1 min)
3300 s (5 min)
4900 s (15 min)
53600 s (1 h)
621600 s (6 h)

An Ihren Server gesendete Benachrichtigungen

EreignisGesendet, wenn
payment.paidEine Zahlung wurde eingezogen.
payment.failedDer erste fehlgeschlagene Versuch einer noch offenen Zahlung. Nicht endgültig: Der Kunde kann es erneut versuchen.
payment.refundedEine Rückerstattung wurde abgeschlossen.
POSTpayment.paid

Signierte Benachrichtigung payment.paid an die Händler-URL

Header

FeldTypErforderlichBeschreibung
X-CAV-Eventstring · headerErforderlich
payment.paid
X-CAV-Environmentstring · headerErforderlich
sandboxlive
X-CAV-Deliverystring · uuid · headerErforderlichID der Zustellung. Bleibt bei Wiederholungen unverändert.
X-CAV-Timestampstring · headerErforderlich
pattern ^[0-9]{10}$
X-CAV-Signaturestring · headerErforderlich
pattern ^[0-9a-f]{64}$

Anfrage-Body · Beispiel

{
  "event_id": "3BAEB108-41A8-40AE-81EF-0A82AA1132D6",
  "event_type": "payment.paid",
  "environment": "sandbox",
  "occurred_at": "2026-10-05T12:01:00.000Z",
  "payment": {
    "code": "a1b2c3d4",
    "merchant_reference": "INV-104",
    "status": "paid",
    "amount": "10.00",
    "currency_code": "USD",
    "created_at": "2026-10-05T12:00:00.000Z",
    "paid_at": "2026-10-05T12:01:00.000Z",
    "card": {
      "brand": "visa",
      "bin": "400000",
      "last4": "0010",
      "holder_name": "TEST CARDHOLDER",
      "issuing_country": "TUR",
      "issuing_bank": "TEST BANK",
      "funding_type": "credit",
      "category": "STANDARD"
    }
  }
}
POSTpayment.failed

Signierte Benachrichtigung über einen fehlgeschlagenen Zahlungsversuch

Anfrage-Body · Beispiel

{
  "event_id": "3BAEB108-41A8-40AE-81EF-0A82AA1132D6",
  "event_type": "payment.failed",
  "environment": "sandbox",
  "occurred_at": "2026-10-05T12:01:00.000Z",
  "payment": {
    "code": "a1b2c3d4",
    "merchant_reference": "INV-104",
    "status": "created",
    "amount": "10.00",
    "currency_code": "USD",
    "created_at": "2026-10-05T12:00:00.000Z",
    "expires_at": "2026-10-06T12:00:00.000Z"
  },
  "failure": {
    "code": "declined",
    "occurred_at": "2026-10-05T12:01:00.000Z",
    "detail_code": null,
    "card": {
      "brand": "visa",
      "bin": "400000",
      "last4": "0010",
      "holder_name": "TEST CARDHOLDER",
      "issuing_country": "TUR",
      "issuing_bank": "TEST BANK",
      "funding_type": "credit",
      "category": "STANDARD"
    }
  }
}

Der Rahmen entspricht payment.paid. payment enthält expires_at statt paid_at, und failure beschreibt den Versuch (siehe Fehlgeschlagene Versuche).

Zum Prüfen von Zustellungen oder zum erneuten Einreihen einer fehlgeschlagenen siehe Webhook-Zustellungen.

Testumgebung

Sandbox-Anfragen arbeiten mit virtuellen Daten und bewegen nie echtes Geld. Öffnen Sie die payment_url und zahlen Sie mit einer der unten aufgeführten Testkarten.

So funktioniert es

Jede mit einem Sandbox-Schlüssel signierte Anfrage läuft auf isolierten virtuellen Daten. Regeln, Felder und Fehlercodes entsprechen dem Live-Betrieb. Die payment_url öffnet dieselbe Zahlungsseite, die Ihre Kunden live sehen. Wird eine Testzahlung genehmigt, geht payment.paid mit environment auf sandbox an die Webhook-URL der Zahlung.

  • Die Seite zeigt einen Testhinweis und akzeptiert nur die unten aufgeführten Testkarten; echte Kartennummern werden abgelehnt. Bei 3-D-Secure-Karten fordert sie Sie auf, die Authentifizierung zu bestätigen oder abzulehnen. Die API nimmt keine Kartennummern an; geben Sie die Karte auf der Zahlungsseite ein.
  • Es sind weder echtes Geld noch echte Karten oder Chain-Transaktionen beteiligt, und nichts erreicht Ihr Live-Guthaben.

Sandbox-Testkarten

Verwenden Sie diese synthetischen Nummern nur auf der Zahlungsseite einer Sandbox-Zahlung, niemals bei einer Live-Zahlung. Geben Sie einen beliebigen Karteninhabernamen mit mindestens 2 Buchstaben, ein Ablaufdatum im aktuellen Monat oder später und eine 3- oder 4-stellige CVV ein.

KartennummerErgebniscodeVerhalten
4000000000000010approvedOhne 3-D Secure genehmigt.
4000000000000051declinedOhne 3-D Secure abgelehnt.
4000000000000002three_ds_approvedDie Zahlungsseite zeigt einen 3-D-Secure-Schritt; bei Bestätigung wird die Zahlung genehmigt, bei Ablehnung wird sie abgelehnt.
4000000000000093three_ds_declinedDie Zahlungsseite zeigt einen 3-D-Secure-Schritt; die Zahlung wird unabhängig von Ihrer Wahl abgelehnt.
4000000000000044timeoutDas Ergebnis bleibt ausstehend (kein Endergebnis); nicht als endgültige Ablehnung behandeln. Der Versuch wird später als abgelaufen geschlossen.

Eine abgelehnte Zahlung bleibt created und sendet beim ersten fehlgeschlagenen Versuch einmal payment.failed; der Kunde kann es erneut versuchen (10 Versuche pro Stunde und IP-Adresse). Die Timeout-Karte und Beträge, die auf .77 enden, lassen das Ergebnis ausstehend: Die Seite zeigt ein ausstehendes Ergebnis, es wird kein Webhook gesendet, und ein Ergebnis wird nie von Hand eingetragen. Nach etwa 30 Minuten wird der Versuch als abgelaufen geschlossen; die Zahlung bleibt created, und der Kunde kann neu beginnen. Die Regel für .77 betrachtet den Betrag in US-Dollar.

Anfrageprotokoll

Im Panel zeigt “Sandbox-Anfrageprotokoll” Ihre letzten Sandbox-Anfragen: die letzten 90 Tage, wobei die Quell-IP 30 Tage gespeichert wird. Anfragen, bei denen die Signatur- oder Zugangsdatenprüfung fehlgeschlagen ist, werden nicht protokolliert; finden Sie sie über die correlation_id in der Antwort. Live-Anfragen werden nicht protokolliert.

Tests für Rückerstattungen, Auszahlungen und Streitfälle sind auf der Seite Kontovorgänge beschrieben.

Fehlercodes

Fehler werden als application/problem+json zurückgegeben. Das Feld code ist einer der unten stehenden Codes; geben Sie bei Anfragen an den Support die correlation_id an. title und detail richten sich nach dem Header Accept-Language; verlassen Sie sich in Ihrem Code nur auf code.

Beispiel einer Fehlerantwort

application/problem+json

{
  "type": "https://www.cryptoavans.com/errors/9000011",
  "title": "Signature headers are missing",
  "status": 400,
  "detail": "X-CAV-Account-No, X-CAV-Api-Key, X-CAV-Timestamp and X-CAV-Signature are mandatory.",
  "code": "9000011",
  "correlation_id": "3BAEB108-41A8-40AE-81EF-0A82AA1132D6"
}

Codetabelle

Geht die Antwort verloren oder erhalten Sie 500 oder 503, kann das Ergebnis unklar sein. Wiederholen Sie mit demselben Idempotency-Key und Body und signieren Sie mit aktuellem Zeitstempel neu. Beachten Sie Retry-After bei 503. Erstellen Sie nicht allein wegen eines Timeouts eine neue Zahlung.

CodeHTTPTitel
9000001401Die Signatur ist ungültig
9000002401Zeitstempel außerhalb des Zeitfensters
9000003401Die Zugangsdaten sind ungültig
9000004403Das Konto kann keine Transaktionen durchführen
9000005422Betrag ist ungültig
9000006422Die Händlerreferenz ist ungültig
9000007409Die Händlerreferenz wurde bereits verwendet
9000008422Die Benachrichtigungsadresse ist ungültig
9000009404Zahlung oder Rückerstattung nicht gefunden
9000010409Idempotenzkonflikt
9000011400Signaturheader fehlen
9000012403IP-Adresse nicht zugelassen
9000013409Sandbox nicht verfügbar
9000014409Rückerstattungs- oder Stornoanfrage besteht bereits
9000015404Streitfall nicht gefunden
9000016409Antwort auf den Streitfall nicht akzeptiert
9000017422Die TRC20-Adresse ist ungültig
9000018422Adressbestätigung erforderlich
9000019422Betrag ist ungültig
9000020409Unzureichendes Guthaben
9000021409Das tägliche Auszahlungslimit wurde überschritten
9000022409Auszahlung wird bearbeitet
9000023404Auszahlung nicht gefunden
9000024404Webhook-Zustellung nicht gefunden
9000025409Erneuter Versuch nicht möglich
9000026503Sandbox-Schema noch nicht bereit
9000027409Sandbox-Guthaben ist belegt
9000030409Sandbox-Gutschrift ungültig
9000033409Unzureichendes Sandbox-Guthaben
9000038422Ungültige Rückgabe-URL
9000039409Zahlung wird vorbereitet
9000040403API-Zugriff ist deaktiviert
1000001422Validierungsfehler gemeinsam
1000002404Kein Datensatz gefunden gemeinsam
1000005429Anfragelimit überschritten gemeinsam
1000006500Serverfehler gemeinsam
1000007405Methode nicht unterstützt gemeinsam
1000009422Ungültiger Idempotenzschlüssel gemeinsam
1000011503Der Dienst ist derzeit nicht erreichbar gemeinsam
1000013503Die Schlüsselinfrastruktur ist nicht bereit gemeinsam
1000014503Dienst in Wartung gemeinsam
1000020422Ungültiges Betragsformat gemeinsam
1000021422Nicht unterstützte Währung gemeinsam
1000023422Betrag überschreitet die Kapazität gemeinsam
1000030422Adresse kann nicht verwendet werden
3000001503Der Kurs ist derzeit nicht verfügbar gemeinsam

Go-live-Checkliste

Arbeiten Sie vor dem Livegang jeden Punkt ab.

  • Sie haben die Abläufe für Zahlung, Webhook, Rückerstattung und Auszahlung in der Sandbox Ende-zu-Ende getestet.

  • Der Live-Schlüssel und das Secret werden nur auf Ihrem Server gespeichert und nicht in Logs geschrieben.

  • Ihre Live-IP-Zulassungsliste und Ihre Live-HTTPS-Webhook-URL sind eingerichtet.

  • Die Webhook-Signaturprüfung ist aktiv und weist ungültige Signaturen ab; der Handler antwortet schnell mit 2xx und toleriert wiederholte Zustellungen.

  • Bestellungen werden nur abgeschlossen, wenn der Webhook oder GET /payments/{code} paid zeigt, nie aufgrund des Browsers des Kunden.

  • Schreibanfragen senden einen Idempotency-Key, und Wiederholungen verwenden ihn erneut; jede Zahlung erhält eine neue merchant_reference.

  • Ihre Serveruhr ist per NTP synchronisiert.

  • Ihrem Konto ist ein Live-Terminal zugewiesen, und Sie haben gemeinsam mit CryptoAvans eine kleine Live-Zahlung durchgeführt.

Sprechen Sie mit Ihrem Account-Manager über das Live-Terminal und den abschließenden Ende-zu-Ende-Test. Zum Panel