Anmelden

Webhook

Wenn jemand über Ihre Zahlungsseite bezahlt, kann Ihr Webshop sofort darüber benachrichtigt werden. Dazu tragen Sie einmalig eine Adresse unter Einstellungen > Zahlungsseite > Integration ein (siehe Zahlungsseite). Ab diesem Moment erhält diese Adresse ein POST mit JSON, sobald mit einer Zahlung etwas geschieht.

Der Aufbau ist bewusst fast identisch mit dem von Mollie. Die Events heißen bis auf eines gleich, die Zahlung im Body sieht gleich aus und die Signatur funktioniert gleich. Arbeitet Ihr Webshop bereits mit einem PSP, erkennen Sie nahezu alles wieder.

Die drei Events

Event Wann
payment.open Der Zahler hat Ihre Zahlungsseite vor sich
payment.claimed Der Zahler sagt, dass er bezahlt hat
payment.paid Die Bank bestätigt, dass das Geld eingegangen ist

payment.open geht raus, sobald die Zahlungsseite wirklich auf dem Bildschirm steht. Das ist etwas anderes als das Öffnen des Links: Die Seite meldet sich erst mit einem Skript an, ein Linkscanner, ein Crawler oder der Prefetch eines Browsers erzeugt also keine Nachricht. Bezahlt ist zu diesem Zeitpunkt noch nichts; dies ist das Startsignal, dass jemand vor Ihrer Zahlungsseite sitzt. Ein Zahler ohne JavaScript meldet sich erst bei ‘Ich habe bezahlt’: Dann folgen payment.open und payment.claimed kurz aufeinander.

payment.claimed geht in dem Moment raus, in dem der Zahler auf ‘Ich habe bezahlt’ drückt oder von einem signierten Zahlungsauftrag bei seiner eigenen Bank zurückkehrt. Der Zahler ist zu diesem Zeitpunkt noch auf der Seite. Das ist noch keine Bestätigung: Es ist der Zahler, der es sagt, nicht die Bank.

payment.paid geht raus, sobald die Überweisung auf Ihrem Konto steht und der Zahlung zugeordnet ist. Das ist die Bestätigung, auf die Sie warten, und der einzige Status, bei dem Sie eine Bestellung freigeben sollten.

Die Liste der Online-Zahlungen in der Kasse benennt den Status einer Zahlung mit denselben drei Wörtern, sodass Sie sie neben das legen können, was Ihr Webshop erhalten hat. Zu einem Besuch, aus dem nichts weiter geworden ist, gehört keine Nachricht und kein Wort: Dort bleibt der Status leer.

Dazu gehört eine Sache, die anders ist als bei einem PSP: Die Kasse erfährt es von Ihrer Bank über Ponto, und Ponto liest Ihr Konto viermal täglich. Rechnen Sie also mit Stunden zwischen payment.claimed und payment.paid, nicht mit Sekunden. Nur Letzteres kommt von der Bank: payment.open und payment.claimed sieht die Zahlungsseite selbst geschehen, die bekommen Sie also auch ohne Bankanbindung. Haben Sie keine, sind das die beiden, die Sie hören, und payment.paid bleibt aus.

Der Aufruf

Ein Aufruf ist ein POST mit Content-Type: application/json und diesen Headern:

Header Inhalt
X-Usecue-Event payment.open, payment.claimed oder payment.paid
X-Usecue-Signature sha256= gefolgt vom HMAC-SHA256 des Bodys
User-Agent Usecue-POS-Webhook/1.0

Im Body steht die Zahlung selbst, so wie sie zu diesem Zeitpunkt dasteht. Der Status im Body ist das Event ohne das vorangestellte payment., genau wie bei Mollie.

payment.open

{
  "resource": "payment",
  "id": "pay_123",
  "mode": "live",
  "amount": { "currency": "EUR", "value": "10.00" },
  "description": "Order 12345",
  "method": "epc",
  "status": "open",
  "createdAt": "2026-09-11T09:13:37+02:00",
  "expiresAt": "2026-09-25T09:13:37+02:00",
  "details": {
    "transferReference": "Order 12345",
    "consumerName": null,
    "consumerAccount": null,
    "consumerAccountDigits": null,
    "remittanceInformation": null,
    "paymentLink": null
  },
  "_links": {
    "dashboard": { "href": "https://pos.usecue.com/pos/payments/details/123", "type": "text/html" },
    "documentation": { "href": "https://pos.usecue.com/p/de/docs/zahlungsseite/webhook/", "type": "text/html" }
  }
}

payment.claimed

{
  "resource": "payment",
  "id": "pay_123",
  "mode": "live",
  "amount": { "currency": "EUR", "value": "10.00" },
  "description": "Order 12345",
  "method": "epc",
  "status": "claimed",
  "createdAt": "2026-09-11T09:13:37+02:00",
  "expiresAt": "2026-09-25T09:13:37+02:00",
  "details": {
    "transferReference": "Order 12345",
    "consumerName": null,
    "consumerAccount": null,
    "consumerAccountDigits": "4300",
    "remittanceInformation": null,
    "paymentLink": null
  },
  "_links": {
    "dashboard": { "href": "https://pos.usecue.com/pos/payments/details/123", "type": "text/html" },
    "documentation": { "href": "https://pos.usecue.com/p/de/docs/zahlungsseite/webhook/", "type": "text/html" }
  }
}

payment.paid

{
  "resource": "payment",
  "id": "pay_123",
  "mode": "live",
  "amount": { "currency": "EUR", "value": "10.00" },
  "description": "Order 12345",
  "method": "epc",
  "status": "paid",
  "createdAt": "2026-09-11T09:13:37+02:00",
  "paidAt": "2026-09-11T14:02:00+02:00",
  "details": {
    "transferReference": "Order 12345",
    "consumerName": "J. Jansen",
    "consumerAccount": "NL91ABNA0417164300",
    "consumerAccountDigits": "4300",
    "remittanceInformation": "Order 12345",
    "paymentLink": null
  },
  "_links": {
    "dashboard": { "href": "https://pos.usecue.com/pos/payments/details/123", "type": "text/html" },
    "documentation": { "href": "https://pos.usecue.com/p/de/docs/zahlungsseite/webhook/", "type": "text/html" }
  }
}

Die Felder

Feld Bedeutung
id Die Zahlung, pay_ gefolgt von der Nummer in der Kasse
mode live, oder test, solange Sie in der Sandbox von Ponto arbeiten
amount.value Der Betrag als String, mit zwei Dezimalstellen
description Die Beschreibung aus dem Zahlungslink
method Womit der Zahler bezahlt hat, siehe unten
status open, claimed oder paid
createdAt Wann der Zahler die Zahlungsseite geöffnet hat
expiresAt Bis wann eine Überweisung noch zu dieser Zahlung gehört
paidAt Wann die Bank das Geld gebucht hat

expiresAt steht in einem payment.open und einem payment.claimed, paidAt nur in einem payment.paid, genau wie bei Mollie. Eine Überweisung, die nach expiresAt eingeht, wird dieser Zahlung nicht mehr zugeordnet; es folgt dann also auch kein payment.paid mehr.

Die method ist eine von diesen:

Method Bedeutung
epc Der EPC-QR-Code, mit der Bank-App gescannt
iban Manuell überwiesen
ponto Ein Zahlungsauftrag, bei der eigenen Bank signiert
fallback Ein Zahlungslink, zum Beispiel Tikkie
noneu Ein Zahlungslink für außerhalb der EU, Stripe oder PayPal

In details steht, was über den Zahler bekannt ist. Bei einem payment.open ist das so gut wie nichts: Der Zahler hat selbst noch nichts eingegeben und die Bank hat noch nichts gesagt. consumerName und consumerAccount kommen von der Bank und sind daher erst bei payment.paid gefüllt. consumerAccountDigits sind die Ziffern, die der Zahler bei einem Zahlungslink selbst eingegeben hat, remittanceInformation ist der Verwendungszweck, so wie die Bank ihn weitergegeben hat, und paymentLink der Name des Zahlungslinks, der auf dem Bildschirm stand.

Die Signatur prüfen

Beim Speichern einer Webhook-URL erzeugt die Kasse ein Webhook-Secret. Es steht auf derselben Seite. Jeder Aufruf ist damit signiert, sodass Sie wissen, dass die Nachricht von uns kommt und unterwegs nicht verändert wurde.

Berechnen Sie den HMAC-SHA256 über den Body, so wie Sie ihn erhalten haben, also nicht über das JSON, nachdem Sie es eingelesen und wieder ausgegeben haben:

<?php
$secret = 'das Webhook-Secret';
$body = file_get_contents('php://input');
$signature = 'sha256=' . hash_hmac('sha256', $body, $secret);

if (!hash_equals($signature, $_SERVER['HTTP_X_USECUE_SIGNATURE'] ?? '')) {
    http_response_code(403);
    exit;
}

$payment = json_decode($body, true);
if ($payment['status'] == 'paid') {
    // hier geben Sie die Bestellung frei
}

http_response_code(200);

Vergleichen Sie mit hash_equals und nicht mit ==. Tun Sie mit einer Nachricht, deren Signatur nicht stimmt, sonst nichts.

Antworten und erneut versuchen

Antworten Sie mit einem 2xx-Status. Alles andere, und auch gar keine Antwort, gilt als nicht angekommen.

  • Ein Aufruf, der nicht angekommen ist, wird erneut versucht, jede Stunde. Fünf Versuche pro Event, den ersten mitgezählt, nach etwa vier Stunden ist also Schluss.
  • Das geschieht pro Event: Dass payment.open nicht angekommen ist, hält payment.claimed oder payment.paid nicht auf.
  • Ein Aufruf, der angekommen ist, wird nie wiederholt.
  • Wir warten höchstens zehn Sekunden auf Ihre Antwort. Bei payment.open und payment.claimed steht der Zahler währenddessen vor Ihrer Zahlungsseite, antworten Sie also sofort und erledigen Sie die eigentliche Arbeit danach.

Was von jedem Versuch zurückkam, können Sie in der Kasse nachlesen, unter der Zahlung selbst bei Online-Zahlungen.

Worauf Sie achten sollten

  • payment.open ist ein Besucher, noch keine Zahlung: Bei den meisten bleibt es dabei. Machen Sie daraus also keine Bestellung. Wofür es gut ist: die zugehörige Bestellung heraussuchen, sehen, dass der Zahler da ist, und später merken, dass er nicht weitergekommen ist.
  • Verlassen Sie sich nicht auf die Reihenfolge: Ein Zahler, der den Knopf nicht drückt, aber überweist, erzeugt ein payment.open und ein payment.paid und kein payment.claimed. Warten Sie also nicht auf eine Nachricht, die vielleicht nie kommt.
  • Eine Bestellung kann mehr als eine id erzeugen: Wer die Zahlungsseite auf einem anderen Gerät öffnet oder noch einmal zurückkommt, nachdem er auf ‘Ich habe bezahlt’ gedrückt hat, beginnt eine neue Zahlung mit einer neuen pay_-Nummer und damit ein neues payment.open. Die description ist das, was all diese Nachrichten mit Ihrer Bestellung verbindet.
  • Schauen Sie auf status, nicht auf die Anzahl der Nachrichten: Theoretisch können Sie dasselbe Event zweimal erhalten, zum Beispiel wenn unsere Antwort unterwegs verloren geht, während Sie bereits gebucht hatten. Nutzen Sie die id als Schlüssel und sorgen Sie dafür, dass eine zweite Nachricht nichts kaputt macht.
  • Geben Sie erst bei paid frei: claimed ist der Zahler, der sagt, dass er bezahlt hat. Für eine Bestellung, die Sie versenden, ist das zu wenig; für ein ‘Wir haben Ihre Bestellung erhalten’ reicht es.
  • Nur über eingehendes Geld: Was Sie selbst überweisen, kommt nicht in die Kasse und damit auch nicht in einen Webhook.

Unterschiede zu Mollie

Die Namen und der Aufbau sind nahezu gleich, aber dies ist kein PSP. Die Unterschiede auf einen Blick:

  • Es gibt drei Events. payment.authorized, payment.canceled, payment.expired und payment.failed gibt es hier nicht: Eine Überweisung, die nicht kommt, kommt einfach nicht.
  • Das mittlere Event heißt hier payment.claimed und bei Mollie payment.pending. Bei einem PSP ist pending eine Zahlung, die unterwegs ist; hier ist es der Zahler, der sagt, dass er bezahlt hat, und das ist etwas anderes.
  • Mollie kennt den Status open auch, ruft Sie dafür aber nicht an. Hier hören Sie ihn: Die Zahlungsseite sieht selbst, dass der Zahler da ist, und das ist der einzige Moment, in dem Sie sicher wissen, dass Ihr Link benutzt wird.
  • Es gibt keine API, um eine Zahlung später abzurufen. Alles, was Sie brauchen, steht im Body; _links.self fehlt deshalb.
  • Die Signatur steht in X-Usecue-Signature statt in X-Mollie-Signature, und das Event in X-Usecue-Event.
  • Wir versuchen es fünfmal, jede Stunde; Mollie zehnmal in 26 Stunden.
  • Zwischen claimed und paid liegen Stunden, weil die Bestätigung von Ihrer eigenen Bank kommt und nicht von einem PSP.

Ausprobieren

Stellen Sie die Bankanbindung in den Sandbox-Modus und Sie erhalten dieselben Nachrichten mit "mode": "test". Bezahlen Sie danach einen Testbetrag auf Ihrer eigenen Zahlungsseite: Das payment.open kommt, sobald die Seite auf Ihrem Bildschirm steht, das payment.claimed, sobald Sie auf ‘Ich habe bezahlt’ drücken, und das payment.paid, sobald Ponto die Testbank gelesen hat.

Ohne Bankanbindung können Sie die ersten beiden genauso ausprobieren: Tragen Sie eine Webhook-URL ein, öffnen Sie Ihre eigene Zahlungsseite und ein payment.open steht sofort bereit.

Kommen Sie nicht weiter? Nehmen Sie Kontakt auf.