Skip to content

Shop-Integration mit QryptoPay

Dieser Artikel setzt die Einrichtung der Zahlungsseite fort. Zu diesem Zeitpunkt sollten das Modul, der Merchant, eine Wallet zum Zahlungsempfang und die Zahlungsseite bereits eingerichtet sein — falls noch nicht, beginnen Sie mit dem Schnellstart.

Es bleibt, den vollständigen Zahlungszyklus zusammenzusetzen: Ihren Shop mit dem Terminal verbinden, Zahlungslinks erzeugen und Zahlungsbenachrichtigungen per Webhook empfangen.

💡 Möchten Sie sich nicht selbst mit der Integration befassen?

Unser Team kann QryptoPay in Ihren Shop einbinden — schreiben Sie uns einfach. Die Kosten hängen von der Komplexität des Projekts ab und davon, worauf es basiert: Sprache, Framework oder CMS.

Terminal-Tokens

Jedes Terminal hat sein eigenes Token-Paar — einen öffentlichen und einen privaten Token: Damit wird bestätigt, dass Zahlungsabsichten und die zugehörigen Benachrichtigungen nicht gefälscht sind. Solange kein Paar generiert ist, kann das Terminal keine Zahlungslinks erstellen.

So generieren Sie es: Öffnen Sie den Merchant, suchen Sie die Karte des gewünschten Terminals und klicken Sie auf das Schlüsselsymbol mit dem Tooltip «Tokens generieren»; klicken Sie dann im Dialog «Tokens für das Terminal erstellen» auf «Generieren». Das Panel zeigt den privaten Token: Kopieren Sie ihn über die Schaltfläche daneben und bestätigen Sie mit «Ich habe ihn kopiert».

⚠️ Der private Token wird nur einmal angezeigt

Er wird bei QryptoPay nicht gespeichert und lässt sich nicht erneut einsehen: Schließen Sie den Dialog, ohne den Token kopiert zu haben, müssen Sie ein neues Paar generieren — und das alte wird mit der Generierung sofort ungültig.

Mehr dazu — worin sich öffentlicher und privater Token unterscheiden und wann ein neues Paar sinnvoll ist — im Artikel Wie man Terminal-Tokens in QryptoPay generiert.

Schritt 1. Einrichtung der Integration

Das zentrale Element der Integration ist das Terminal. Jedes Terminal verfügt über die folgenden Parameter:

  • ID — eine eindeutige Kennung;
  • Token-Paar — ein öffentlicher und ein privater Token (zur Identifizierung von Zahlungen);
  • Webhook — die URL Ihres Shops, an die QryptoPay Benachrichtigungen über den Zahlungsstatus sendet;
  • Webhook-Schlüssel — wird zur Autorisierung der Webhook-Benachrichtigungen verwendet.

⚠️ Wichtig

Der private Token muss sicher aufbewahrt werden. Wird er kompromittiert, können Dritte in die Zahlungslogik Ihres Shops eingreifen. Aus diesem Grund speichert QryptoPay den privaten Token nach der Generierung nicht auf seiner Seite.

Bei Verlust oder Kompromittierung können Sie jederzeit ein neues Token-Paar generieren. Vergessen Sie dabei nicht, die Werte in Ihrem Shop zu aktualisieren.

Der Webhook-Schlüssel ist weniger kritisch, sollte jedoch ebenfalls nicht an Dritte weitergegeben werden, um manipulierte Webhook-Benachrichtigungen zu vermeiden.

Wir empfehlen, die Integration zunächst mit dem Testterminal zu beginnen. Im Gegensatz zum Hauptterminal können Sie damit die korrekte Funktion der Integration prüfen, ohne tatsächlich Kryptowährungen zu übertragen.

Generieren Sie zunächst ein Token-Paar für das Testterminal und speichern Sie den privaten Token sicher ab.

Speichern Sie anschließend die Terminal-Parameter in den Umgebungsvariablen Ihres Shops. Eine .env-Datei kann beispielsweise wie folgt aussehen:

TERMINAL_ID: <Terminal-ID>
PRIVATE_TOKEN: <generierter privater Token>
WEBHOOK_KEY: <Webhook-Schlüssel aus den Einstellungen>
PAYMENT_URL: <Domain der Zahlungsseite>

Als Nächstes müssen Sie eine Methode implementieren, die einen Zahlungstoken erzeugt, der zum Erstellen eines Zahlungslinks auf der QryptoPay-Seite verwendet wird.

Nachfolgend sehen Sie ein Beispiel für die Implementierung einer solchen Methode in Pseudocode:

function generate_payment_token(private_key_b64, terminal_uuid) -> string
  # dekodiert den privaten Schlüssel von base64/base64url in einen Seed (Rohbytes)
  seed := decode_base64_any(private_key_b64)

  # generiert einen eindeutigen Nonce (in der Regel uuid4)
  nonce := uuid_v4()

  # erstellt den Payload (Pflichtfelder + Zahlungsdaten)
  payload := {
    ts: current_unix_time_seconds(),
    nonce: nonce,
    terminal_uuid: terminal_uuid,     // Terminal-ID in QryptoPay

    amount_fiat: transaction.amount,  // Zahlungsbetrag in USD, Format 00.00, decimal, > 0
    payment_mid: transaction.uuid,    // Transaktions-ID in Ihrem Shop, string
    back_to_store_link: link,         // Link zurück zu Ihrer Website nach der Zahlung (Parameter können optional ergänzt werden), string

    customer: {
      id: customer.uuid,              // Kunden-ID aus Ihrem System (erforderlich), string
      email: customer.email or ""     // Kunden-E-Mail aus Ihrem System (optional), string
    },

    metadata: {                       // beliebige zusätzliche Parameter, die Sie übergeben möchten
      key: value                      // im Format metadata.key=value
    }
  }

  # serialisiert den Payload in eine deterministische (kanonische) Byte-Darstellung
  payload_bytes := canonical_encode(payload)

  # kodiert den Payload in ein für die Übertragung geeignetes Format
  payload_part := base64url_no_padding(payload_bytes)

  # berechnet den kryptografischen Teil des Tokens basierend auf dem privaten Schlüssel und payload_part
  proof_bytes := sign(seed, bytes(payload_part, ASCII))

  # kodiert den kryptografischen Teil des Tokens
  proof_part := base64url_no_padding(proof_bytes)

  # endgültiges Token-Format: "<payload_b64u>.<proof_b64u>"
  return payload_part + "." + proof_part
end

⚠️ Wichtig

Für die Generierung des Zahlungstokens müssen Sie zusätzlich zu den Transaktionsdaten drei Pflichtparameter übergeben: den aktuellen Zeitstempel, einen nonce (empfohlen: uuid4) und die Terminal-ID.

Der Zeitstempel wird verwendet, um die Gültigkeitsdauer des Links zu berechnen (etwas mehr als 1 Stunde). Der nonce dient der Sicherheit und verhindert, dass aus demselben Link mehrere Zahlungs-Intents erstellt werden (Spamschutz). Ohne die Terminal-ID kann QryptoPay die Zahlung nicht verarbeiten.

Verwenden Sie den erzeugten Token und senden Sie eine POST-Anfrage an QryptoPay, um einen Zahlungslink zu erhalten. Zum Beispiel:

POST /public/api/payments/intents/create/
Host: qpay.yoursite.com
Content-Type: application/json

{
  "key": "Zahlungstoken"
}

⚠️ Wichtig

Zum Zeitpunkt der Anfrage muss die Zahlungsseite bereits auf dem Server erstellt sein, auf dem QryptoPay installiert ist.

Wenn die Anfrage erfolgreich ist, erhalten Sie eine Antwort mit einem Zahlungslink. Diesen Link müssen Sie an den Kunden weitergeben, damit er zur Zahlung wechseln kann.

{
  "service_id": "be535ba0-7f84-4cd3-9454-b26c4a938479",
  "url": "https://qpay.yoursite.com/?payment=be535ba0-7f84-4cd3-9454-b26c4a979225",
  "expires_at": "2026-01-30T08:21:56.526112Z"
}

Im Fehlerfall kann der Server einen der folgenden Codes zurückgeben:

  • 400 — ungültige Anfrage (Formatfehler oder fehlende Pflichtparameter).
  • 403 — ungültige Anfragesignatur oder der Zahlungstoken ist abgelaufen.
  • 409 — erneute Verwendung eines Einmal-Tokens. Der übergebene nonce wurde für dieses Terminal bereits verwendet.
  • 444 — die Erstellung eines Zahlungs-Intents ist vorübergehend nicht erlaubt (z. B. aufgrund von Lizenzbeschränkungen oder überschrittener Limits).
  • 445 — die Erstellung von Zahlungs-Intents ist nicht erlaubt — die Lizenz ist ausgeschöpft oder der Zugriff dauerhaft eingeschränkt.
  • 500 — interner Serverfehler.

⚠️ Wichtig

Wenn der Kunde dem Zahlungslink folgt, eine Währung auswählt und den Zahlungsvorgang startet (auf Weiter klickt), kann die ausgewählte Währung nicht mehr geändert werden. In diesem Fall muss der Kunde zu Ihrer Website zurückkehren und einen neuen Zahlungslink erstellen. Dieses Verhalten ist aus Sicherheitsgründen implementiert.

⚠️ Wichtig

Wenn Sie die E-Mail-Adresse des Kunden im Payload an QryptoPay übermitteln und sich diese E-Mail später ändert, identifiziert QryptoPay den Kunden anhand der ID und überschreibt die gespeicherte E-Mail bei einer Abweichung. Die aktualisierte E-Mail wird auch auf zuvor erstellte Zahlungen angewendet.

Der allgemeine Ablauf zur Generierung eines Zahlungslinks sieht wie folgt aus:

  • in Ihrem Shop wird ein Zahlungs-Intent erstellt (z. B. eine Transaktion);
  • der zu zahlende Betrag wird in USD umgerechnet (QryptoPay akzeptiert eingehende Beträge ausschließlich in USD);
  • es wird ein Zahlungstoken mit den erforderlichen Parametern erzeugt und an Ihren Server mit QryptoPay gesendet;
  • QryptoPay gibt einen Zahlungslink zurück, den Sie an den Kunden weitergeben, damit er zur Zahlung wechseln kann.

Anschließend führt der Kunde die Zahlung auf der Zahlungsseite durch und kehrt bei Bedarf in den Shop zurück. Die Benachrichtigung über eine erfolgreiche oder fehlgeschlagene Zahlung wird mit einer Verzögerung an Ihr Shop gesendet, da Blockchain-Transaktionen auf Netzwerkbestätigungen warten müssen. Dies dauert in der Regel 1–2 Minuten (Tron, USDT TRC-20) bis zu 10–30 Minuten (Bitcoin).

Nun können Sie einen Zahlungslink erstellen, ihn öffnen, eine Währung auswählen und den Zahlungsvorgang starten. Wenn alles korrekt konfiguriert ist, wird die Zahlung in Ihrem QryptoPay-Merchant im Test-Terminal angezeigt.

Schritt 2. Webhook einrichten

Nachdem der Kunde die Zahlung abgeschlossen hat, beginnt QryptoPay damit, die Blockchains nach der entsprechenden Transaktion zu durchsuchen. Wenn die Zahlung strikt gemäß den Anweisungen auf der Zahlungsseite durchgeführt wurde, wird eine Benachrichtigung über die erfolgreiche Zahlung an Ihren Webhook gesendet.

Die Benachrichtigung wird als HTTP-Anfrage mit folgenden Parametern gesendet:

POST /your/webhook/url
Content-Type: application/json

X-Term-UUID: 2671f44b-a025-44d3-b2f1-a0ea07b8acb7
X-Timestamp: 1738150000
X-Body-SHA256: 9c4d2b0a2f5b7d6c8a...   # 64 Hex-Zeichen
X-Signature: 7f1c3d5e9a...             # 64 Hex-Zeichen

{
  ... JSON-Webhook-Payload ...
}

Für die Verarbeitung der Benachrichtigungen benötigen Sie eine Methode, die die Webhook-Signatur validiert und sicherstellt, dass die Anfrage nicht abgefangen oder verändert wurde. Nachfolgend sehen Sie ein Beispiel für eine solche Prüfung in Pseudocode:

function validate_webhook_response() -> bool
  # baut die HMAC-Nachricht: "<terminal_uuid>:<ts>:<body_sha256_hex>"
  message_str := string(term_uuid_header) + ":" + string(ts) + ":" + computed_body_hash
  message_bytes := utf8_bytes(message_str)

  # berechnet die erwartete Signatur: HMAC-SHA256(secret, message), hex
  expected_sig := hmac_sha256_hex(key = utf8_bytes(secret), msg = message_bytes)

  # vergleicht die Signaturen in konstanter Zeit
  if not constant_time_equals(expected_sig, sig_header) then
    return false
  end

  return true
end

⚠️ Wichtig

Wir empfehlen, die Webhook-Anfrage als ungültig zu behandeln, wenn mindestens eine der folgenden Bedingungen erfüllt ist:

  • einer der erforderlichen Header fehlt;
  • X-Term-UUID stimmt nicht mit der Terminal-ID überein, die beim Generieren des Zahlungstokens für diese Transaktion verwendet wurde;
  • X-Timestamp ist keine Zahl (int) oder seit dem angegebenen Zeitstempel sind mehr als 300 Sekunden vergangen (empfohlener Wert).

Wenn die Prüfung erfolgreich ist, können Sie den Request-Body deserialisieren. Sie erhalten ein Objekt in etwa der folgenden Form:

{
  "payment_result": "payment_result",  // Zahlungsergebnis: success, mismatch, unexpected
  "amount_coins": "0.00",             // Betrag in Kryptowährung, Format 00.00
  "expected_amount_coins": "0.00",     // erwarteter Betrag laut Rechnung in Kryptowährung, Format 00.00
  "is_underpaid": false,               // true, wenn weniger als erwartet eingegangen ist
  "amount_fiat": "0.00",              // Betrag in USD, Format 00.00
  "surcharge_fiat": "0.00",           // einbehaltene Gebühr, in USD, Format 00.00; 0.00, wenn die Gebühr deaktiviert ist
  "amount_fiat_net": "0.00",          // Nettobetrag nach Abzug der Gebühr, in USD, Format 00.00; amount_fiat = amount_fiat_net + surcharge_fiat
  "fiat_code": "USD",                 // Fiat-Währung
  "coins_asset": "USDC",              // Kryptowährung: BTC, ETH, USDT, USDC usw.
  "coins_chain": "ETH",               // Netzwerk/Chain: BTC, ETH, TRX usw.
  "service_id": "qryptopay_pi_uuid",  // interner Zahlungs-Identifier in QryptoPay
  "payment_mid": "string",            // Transaktions-ID in Ihrem Shop; null bei Status unexpected
  "customer": {
    "id": "your_id",                  // Kunden-ID in Ihrem System
    "email": "string"                 // Kunden-E-Mail; null, wenn nicht übergeben
  },
  "metadata": {                       // null, wenn ursprünglich nicht übergeben oder bei Status unexpected
    "key1": "value1"
  },
  "transaction_ids": [                // zugehörige Blockchain-Transaktionen (eine oder mehrere)
    "686...fbe",
    "8be...6ab"
  ]
}

Die Felder surcharge_fiat und amount_fiat_net werden immer übertragen, auch wenn die Gebühr für Kryptozahlungen deaktiviert ist — in diesem Fall ist surcharge_fiat gleich 0.00, und amount_fiat_net entspricht amount_fiat, sodass bereits implementierte Integrationen weiterhin funktionieren. Mehr dazu, wie die Gebühr berechnet wird, erfahren Sie im Artikel Währungsgebühren in QryptoPay.

Die Felder expected_amount_coins und is_underpaid werden ebenfalls immer übertragen, unabhängig vom Szenario und selbst wenn die zulässige Unterzahlung deaktiviert ist — aus demselben Grund funktionieren bereits implementierte Integrationen weiter. Was diese Einstellung ist, erfahren Sie weiter unten im Abschnitt «Zulässige Unterzahlung».

⚠️ Wichtig

Wenn die Validierung erfolgreich ist, antworten Sie mit 200 OK. Andernfalls wird QryptoPay die Zahlungsbenachrichtigung erneut senden, bis eine 200-Antwort zurückgegeben wird.

Die empfangenen Daten können Sie für die Nachbearbeitung der Zahlung verwenden. Ein Teil der Parameter wird im Originalformat zurückgegeben — zum Beispiel alle metadata-Werte, die über den Zahlungslink übergeben wurden.

⚠️ Wichtig

Wenn die E-Mail-Adresse des Kunden bereits in QryptoPay gespeichert ist, aber bei der aktuellen Zahlung keine E-Mail übergeben wird, enthält die Webhook-Benachrichtigung die E-Mail aus der QryptoPay-Datenbank.

💡 Tipp

Wir empfehlen, die Webhook-Daten zusätzlich zu validieren, indem Sie sie mit den ursprünglichen Transaktionsparametern abgleichen (z. B. amount_fiat_net mit dem Bestellbetrag, Zahlungs-ID und Kunden-ID).

Als Nächstes müssen Sie auf der Seite Ihres Shops eine Methode implementieren, die die eingehende Benachrichtigung korrekt verarbeitet. Es gibt drei mögliche Szenarien:

  • success — die Zahlung war erfolgreich, auch dann, wenn etwas weniger als der Rechnungsbetrag eingegangen ist, aber innerhalb der zulässigen Unterzahlung (sofern aktiviert — mehr dazu weiter unten). In diesem Fall empfehlen wir, den im Webhook gelieferten Wert amount_fiat_net (Betrag nach Abzug der Gebühr für Kryptozahlungen) vor der Finalisierung der Zahlung mit Ihrem internen Bestellbetrag abzugleichen — amount_fiat kann den Bestellbetrag um diese Gebühr übersteigen;
  • mismatch — der Kunde hat mehr als den erwarteten Betrag bezahlt oder weniger, als die zulässige Unterzahlung erlaubt;
  • unexpected — der Kunde hat Gelder an die Wallet gesendet, ohne zuvor einen Zahlungslink zu erstellen.

Die Verarbeitungslogik für jedes Szenario können Sie selbst festlegen. Beispielsweise können Sie bei mismatch den Betrag aus der Benachrichtigung mit dem erwarteten Betrag vergleichen und den Kunden bei einer unvollständigen Zahlung zur Nachzahlung auffordern. Bei unexpected ist es möglich, den Kontostand des Kunden automatisch aufzufüllen und ihn anschließend über die Zahlung zu informieren.

Nachdem Sie den Handler implementiert haben, tragen Sie die Webhook-URL in den Einstellungen des Test-Terminals ein und wiederholen Sie den Zahlungsablauf: Erstellen Sie einen neuen Zahlungslink und öffnen Sie ihn. Im Test-Terminal wird die Zahlung sofort verarbeitet, und die Benachrichtigung wird umgehend an den Webhook gesendet.

Wenn die Benachrichtigung auf der Seite Ihres Shops erfolgreich empfangen wurde, kann die Test-Integration als abgeschlossen betrachtet werden.

Zulässige Unterzahlung

Manchmal überweist der Kunde etwas weniger als den Rechnungsbetrag: Die Wallet hat die Überweisung gerundet oder einen Teil für die Netzwerkgebühr einbehalten, und es kommen statt 102 USDT nur 101,99 an. Standardmäßig wird eine solche Rechnung nicht geschlossen — QryptoPay wartet auf den vollen Betrag. Wenn Sie bereit sind, solche Zahlungen anzunehmen, aktivieren Sie die zulässige Unterzahlung: Dann kommt die Zahlung mit payment_result: "success" und is_underpaid: true am Webhook an, und Shops, die success bereits als Zahlungsbestätigung werten, schließen die Bestellung ohne Anpassungen ab.

Um die Option zu aktivieren, öffnen Sie QryptoPay«Einstellungen»«Währungen», aktivieren Sie den Schalter im Block «Zulässige Unterzahlung» und legen Sie fest, um wie viel weniger der Kunde zahlen darf: einen Prozentsatz des Rechnungsbetrags und bei Bedarf eine maximale Unterzahlung in USD.

Um eine solche Zahlung von einer exakt in Höhe des Rechnungsbetrags bezahlten zu unterscheiden, gleichen Sie amount_coins (wie viel eingegangen ist) mit expected_amount_coins (wie viel laut Rechnung erwartet wurde) ab und prüfen Sie is_underpaid. Den Fehlbetrag übernimmt der Shop — er wird von amount_fiat_net abgezogen, während die Gebühr für Kryptozahlungen, sofern konfiguriert, vollständig einbehalten wird (mehr dazu im Artikel Währungsgebühren in QryptoPay). Eine Überzahlung kommt als mismatch an.

Schritt 3. Vollständigen Zahlungszyklus prüfen

Bevor Sie echte Zahlungen annehmen, durchlaufen Sie den gesamten Kundenweg mit dem Test-Terminal:

  1. Erstellen Sie in Ihrem Shop eine Bestellung und generieren Sie dafür einen Zahlungslink.
  2. Folgen Sie dem Link — die Zahlungsseite auf Ihrer Domain öffnet sich.
  3. Wählen Sie eine Währung und bestätigen Sie die Zahlung.
  4. Warten Sie auf die Webhook-Benachrichtigung: Im Test-Terminal wird die Zahlung sofort verarbeitet, eine echte Kryptowährungsüberweisung ist nicht nötig.
  5. Prüfen Sie, dass die Bestellung in Ihrem Shop als bezahlt gekennzeichnet wird.

Sind alle fünf Punkte erfüllt, funktioniert die Integration vollständig: Links werden erstellt, die Seite nimmt Zahlungen an, der Shop erfährt vom Zahlungseingang. Bricht der Ablauf mittendrin ab, suchen Sie die Ursache genau an dieser Stelle: Erscheint die Zahlung nicht im Merchant, liegt es am Zahlungstoken; erscheint die Zahlung, aber die Benachrichtigung bleibt aus, liegt es am Webhook-Handler.

Schritt 4. Wechsel auf das Produktiv-Terminal

Jetzt können Sie echte Zahlungen annehmen. Gehen Sie dazu wie folgt vor:

  • generieren Sie ein neues Token-Paar für das Hauptterminal;
  • bearbeiten Sie die Terminal-Einstellungen und tragen Sie die Webhook-Adresse ein;
  • ersetzen Sie in der .env-Datei Ihres Shops die Werte ID, PRIVATE_TOKEN und WEBHOOK_KEY durch die Daten des Hauptterminals.

Anschließend können Sie die Funktion des Terminals mit einer Testzahlung überprüfen. Bitte beachten Sie: In diesem Schritt ist eine echte Kryptowährungsüberweisung erforderlich — und zwar in der Währung, für die Sie ein Wallet hinzugefügt haben. Wir empfehlen, mit kleinen Beträgen zu beginnen.

Wenn alle Einstellungen korrekt vorgenommen wurden, wird die Zahlung erfolgreich in Ihrem Shop verbucht.

Was kommt als Nächstes

Der Shop ist angebunden und nimmt Zahlungen über das Produktiv-Terminal an. Als Nächstes — wie Sie das Modul verwalten:

BeAdmin © 2025. Alle Rechte vorbehalten.