Webhooks
Our calls to YOU — the end of a session, and a chargeback raised against it. Format, retries, header, idempotency.
In the HTTP API reference, you are always the caller. Here it is us: we call your server to tell it what happened. So this page is not part of the OpenAPI contract, which describes what we serve.
It is also the only place you learn what the payment page never sees: the PSP reference of each operation, the card network a tranche ran on, and the card alias when the order asked to register your customer’s means of payment. The example below carries none: an order that registers the means is not split, since one mandate covers the whole order.
Two messages, one field to tell them apart
Section titled “Two messages, one field to tell them apart”The event field says which one you are reading:
event |
when |
|---|---|
session-ended |
a session finished |
chargeback |
a PSP raised a dispute, weeks later |
Switch on it from day one: we will be able to add fields without breaking anything on your side, and the ending you already handle will have nothing new to configure.
Where we call
Section titled “Where we call”Declare one address once on your account and every session of yours is announced there. That is the simplest shape, and the one to reach for.
You can also give one session its own notificationUrl on POST /api/sessions, and that one
wins. Useful when one flow has to be routed somewhere else. With neither an account address nor a
session address, the ending is not announced.
Either way, the address is fixed when the session is created. An ending reaches the endpoint that was agreed when the order was placed, not the one you changed since.
session-ended: a session finished
Section titled “session-ended: a session finished”We call once per ending, with one of these four statuses — nothing else is announced:
status |
what happened |
|---|---|
PAID |
the order is paid |
ABORTED |
your customer gave up |
EXPIRED |
the session reached its time limit |
AWAITED_REFUSED |
a lender refused the financing |
AWAITED_REFUSED deserves a word. An Oney file can leave your customer waiting on the lender’s
verdict, sometimes for days. If the lender accepts, the session ends PAID — the ending you
already handle, just later. If it refuses, or if the wait reaches its bound, the session ends
AWAITED_REFUSED: nothing was taken, captured is zero. Your customer left the page long ago, so
this call is probably your only way to learn it.
{ "event": "session-ended", "notificationId": "019f18f1-cbf4-762f-bd94-776f523c9ffb:PAID", "orderId": "EP-123456", "status": "PAID", "total": { "amount": 100000, "currency": "EUR" }, "captured": { "amount": 100000, "currency": "EUR" }, "terms": [{ "label": "esf des Ménuires", "url": "https://shop.example/terms.pdf" }], "termsAcceptedAt": 1730900400000, "attempts": [ { "attemptId": "019f18f2-6ceb-729c-98b4-d60271a718e0", "method": "card", "methodId": "card", "cardScheme": "visa", "provider": "dalenys", "status": "CAPTURED", "requested": { "amount": 60000, "currency": "EUR" }, "granted": { "amount": 60000, "currency": "EUR" }, "operations": [ { "type": "authorization", "status": "succeeded", "amount": { "amount": 60000, "currency": "EUR" }, "providerRef": "A180635471", "statusCode": "0000", "statusMessage": "Successful Operation" }, { "type": "capture", "status": "succeeded", "amount": { "amount": 60000, "currency": "EUR" }, "providerRef": "A180635474", "statusCode": "0000", "statusMessage": "Successful Operation" } ] }, { "attemptId": "019f18f3-418c-729c-98b4-e2d7be7c6163", "method": "wero", "methodId": "wero", "provider": "payplug-retail", "status": "CAPTURED", "requested": { "amount": 40000, "currency": "EUR" }, "granted": { "amount": 40000, "currency": "EUR" }, "operations": [ { "type": "payment", "status": "succeeded", "amount": { "amount": 40000, "currency": "EUR" }, "providerRef": "pay_6GEUtvaN0NH68vioAWb9LF" } ] } ]}A €1,000 order, paid in two tranches: €600 by card, then €400 with Wero. That is a split payment as your server sees it.
provider says whose dashboard the tranche’s providerRefs live in: a reference alone does
not tell you which back-office to open. The tranche records it when it opens, so it is always
present, and it never moves afterwards even if your configuration changes.
How many operations, and of which type, depends on the method and the PSP — the example above
shows both shapes. A card at Dalenys plays out in two steps, an authorization then a capture,
each with its own reference. A Wero payment settles in a single payment operation. So do not
wait for a capture to know a tranche went through: the tranche’s own status says so, whichever
path it took.
cardScheme gives the network — visa, cb, mastercard, amex… — as the PSP named
it, not as the page guessed it. On a co-badged card it is the network the payment actually ran
on: the one that decides your interchange, and the one your customer’s statement will show.
Absent, it means the verdict named no network we could place — never that the tranche was not a
card, which the method beside it already says.
terms and termsAcceptedAt hand you back your own documents and the instant your customer
accepted them — the proof of a consent you asked for, given to the party who may one day have to
produce it. terms is the list as it stood when they accepted, wording and links included, so it
survives a catalogue that has moved since. termsAcceptedAt alone is absent when a payment page
never gathered the consent: an honest silence rather than an invented hour, and a sign your
integration composed no <payplug-terms>. Both absent means the session declared no terms.
statusCode and statusMessage carry the PSP’s verdict as the PSP said it: the code you can
search their dashboard for, and the sentence beside it. We never recompose it, which has a visible
consequence above — Dalenys names even its successes (0000 Successful Operation), PayPlug says
nothing on a successful payment, and both fields are then simply absent. A failure our platform
produced itself carries statusMessage alone: there is no PSP code to give, and we do not invent
one.
What we expect of your endpoint
Section titled “What we expect of your endpoint”Answer OK
Section titled “Answer OK”We count a delivery as received on HTTP 200 with the body OK, in text/plain. A 2xx alone
says something answered, not that you understood: an OK lost inside an error page is exactly
what this rule catches.
Anything else — another status, another body, a network failure — is retried at t+1 second, t+1 minute, then t+5 minutes. After that we stop, and someone is told on our side.
Check the header, whole
Section titled “Check the header, whole”We send the value you agreed with us on the authorization header, verbatim, scheme included.
Compare the whole string, in constant time.
Never normalize one side only: stripping a Bearer off what you receive while your stored secret
keeps it rejects every legitimate call, while looking perfectly healthy.
That secret is a bearer: it proves nothing about the body, and anyone who intercepts it can
impersonate us. Your endpoint must therefore be https. We refuse a plain-http address when the
session is created, except for hosts that cannot exist on the public internet (localhost, a
container name), so local development stays possible.
Be able to receive the same message twice
Section titled “Be able to receive the same message twice”notificationId does not change across retries: the same ending always carries the same id. If
your acknowledgement is lost on the way back, we come again with that one. Key your work on it, or
a retry will ship the order a second time.
Do not use orderId for this: one order can produce two endings — a session that expired, then a
second one that got paid — and collapsing them would swallow the second, which is the one that
took the money.
Chargebacks
Section titled “Chargebacks”A chargeback is your customer’s bank taking the money back, weeks or months after the payment. We receive the dispute and relay it to you as it stands: nothing is handled automatically on our side.
These messages are on request: declare a chargeback address on your account. Without one you receive nothing, and that is a perfectly normal answer — a disputes team is rarely the one that handles the payment funnel, and we will not push chargebacks at an address that did not ask for them.
{ "event": "chargeback", "notificationId": "A180635474:chargeback:2026-08-18:60000", "orderId": "EP-123456", "sessionId": "019f18f1-cbf4-762f-bd94-776f523c9ffb", "attemptId": "019f18f2-6ceb-729c-98b4-d60271a718e0", "transactionId": "019f18f2-73ee-729c-98b4-dd336f0beb22", "type": "chargeback", "amount": { "amount": 60000, "currency": "EUR" }, "provider": "dalenys", "providerRef": "A180635474", "reasonCode": "13.1", "status": "First chargeback", "date": "2026-08-18", "validUntil": "2026-09-15"}type says which way the money went. chargeback is a debit: it left your account.
representment is a credit: the dispute was settled in your favour and the money comes back.
Reading amount alone would count both the same way.
The attachment is the point. At the PSP, a chargeback only names one of their own references. We resolve it to the order you know, the session it belonged to and the exact tranche the money moved on — so you can act without having to search. A chargeback we cannot attach to anything of yours is never sent: it is worked out by hand on our side.
validUntil is a deadline. Past that date the dispute is usually lost by default. That is why
this delivery follows the same rules as an ending — OK on 200, retries at t+1 s, t+1 min,
t+5 min — and why someone is told on our side if we cannot reach you.
Idempotency works the same way. notificationId is stable across retries, and a PSP resending
the same chargeback does not produce a second message. Two chargebacks that differ by amount or by
date on the same transaction, on the other hand, are two chargebacks, and both arrive.
What we do not do: change the session’s status, correct what it says it captured, or reduce what remains refundable. It stays exactly as it finished. A chargeback is accounted for outside this platform, and this message is all we do about it.