Webhooks
Webhooks are HTTP POST notifications that Avirato Payments sends automatically to your system when a relevant event occurs: a payment completes, a refund is processed, a capture is confirmed, etc.
Why they matter
Webhooks are the most reliable way to know that an operation has completed. Unlike the redirect (urlOk/urlKo), which depends on the customer returning to your page:
- Webhooks arrive always, even if the customer closes the browser
- They arrive a few seconds after the processor confirms the operation
- They do not depend on user behavior
Recommendation: Use webhooks as the primary source for operation confirmation. Use redirect and polling as complements.
How to configure your webhook URL
From Integrations > Webhook Configuration in the dashboard you can register a public URL per environment (Live and Test). Each URL can be enabled/disabled separately. We recommend:
- HTTPS URL with a valid certificate.
- Endpoint dedicated to Avirato Payments (for example
https://your-domain.com/avirato-payments/webhooks). - You need an active API key for the environment before configuring its URL. If none exists, the dashboard responds with 422.
Important: only webhooks generated from the moment the URL is configured and active are sent. Previous events are not resent retroactively.
Authenticity verification (HMAC signature)
All live webhooks arrive signed with HMAC-SHA256 using the same client_secret shown when you create (or rotate) the API key for the environment. Headers:
| Header | Value |
|---|---|
X-Avirato-Timestamp | Unix timestamp (seconds) at send time |
X-Avirato-Signature | t={timestamp},v1={hmac} — where hmac = hash_hmac('sha256', timestamp + "." + body, client_secret) |
PHP verification example:
$timestamp = $_SERVER['HTTP_X_AVIRATO_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_AVIRATO_SIGNATURE'] ?? '';
$body = file_get_contents('php://input');
if (!preg_match('/^t=(\d+),v1=([a-f0-9]+)$/', $signature, $m)) {
http_response_code(400);
exit;
}
$expected = hash_hmac('sha256', $timestamp . '.' . $body, $clientSecret);
if (!hash_equals($expected, $m[2])) {
http_response_code(401);
exit;
}In Test, if you have a test API key created, test webhooks are also signed with its secret. If not, they are sent without a signature so you can explore the format without creating a test API key yet.
Retry policy
How it works in one line
Each time Avirato Payments sends you a webhook and your endpoint does not respond with HTTP 200, it retries later. The wait between each failure and the next retry grows: it starts at 10 minutes and ends at 48 hours. After a total of 10 unsuccessful attempts Avirato Payments stops retrying automatically.
Full schedule
The table shows, for a webhook that fails on every attempt, the exact moment your endpoint receives the next call, counted from when the original event occurred (authorization, refund, etc.).
| Attempt | What it is | When you receive it (from the event) |
|---|---|---|
| 1 | Initial delivery | at the moment (≈ 0) |
| 2 | 1st retry | 10 min later |
| 3 | 2nd retry | 30 min later |
| 4 | 3rd retry | 1 h 10 min later |
| 5 | 4th retry | 2 h 40 min later |
| 6 | 5th retry | 5 h 40 min later |
| 7 | 6th retry | 11 h 40 min later |
| 8 | 7th retry | 23 h 40 min later |
| 9 | 8th retry | 1 day 23 h 40 min later |
| 10 | 9th (and last) retry | ≈ 4 days later (95 h 40 min) |
| — | — | from here on it is no longer retried |
There are 10 attempts in total (1 initial delivery + 9 retries) spread over about 4 days. The clock does NOT reset on each attempt: the later you respond correctly, the more spaced out the subsequent retries have been.
How to know which attempt we are on
In the dashboard, under Integrations > Live > Webhooks, each row in the history shows:
Attempts: number of attempts consumed so far (1 to 10).Last attempt: date and time of the last delivery.HTTP: HTTP code your endpoint returned on that last delivery.Status:Delivered(success),Pending(retries still ahead), orFailed(not applicable in the live flow while retries remain).
What happens if the endpoint keeps failing
If after 10 attempts (≈ 4 days) your endpoint has not responded correctly even once, Avirato Payments stops retrying automatically even if you fix your system later. In that case:
- Reactivate your endpoint and verify it now responds correctly to normal webhooks.
- For events that remained pending, open Integrations > Live > Webhooks, identify webhooks with
Pendingstatus, and force manual resend from the "Resend" button. Each manual resend makes a new HTTP call to your endpoint with the same payload.
What happens if you temporarily disable or delete your URL
- While your URL is disabled (or does not exist) Avirato Payments continues to queue webhooks for events that occur.
- The cron still counts retries: if you reactivate the URL within the ≈ 4 day window, pending ones will be delivered automatically on the next tick (as long as attempts remain available for that specific webhook).
- After that window, events are abandoned and are NOT sent again even if you reactivate the URL. You will need to request them manually from the dashboard if you need them.
Manual resend behavior
- If you manually resend a webhook that was pending retries and the resend succeeds, automatic retries stop — the webhook is considered delivered.
- You can resend a webhook that was already delivered successfully. The dashboard will ask for confirmation first to avoid accidental duplicates, but the resend still runs if you confirm.
Source of truth for payment status (Live)
In the Live environment, processor notifications have the final say on the real payment status. This means a payment can change status after your system has treated it as completed.
Typical cases where a later webhook may arrive that reverses a previous success:
- Chargeback: the cardholder disputes the charge with their bank
- Fraud detected later: the processor identifies a fraudulent transaction days later
- Bank reversal: the issuing bank reverses the operation
- Processor correction: a previous notification contained incomplete or incorrect data
When we receive one of these events in Live, we update the payment status in our system (for example, from AUTHORISED to FAILED) and send you the corresponding webhook. Your integration must:
- Accept asynchronous webhooks that change the outcome of a previously successful payment
- Treat webhooks (not the immediate response or redirect) as the reference for reconciliation and final status
- Query the API again (
GET /payment/session/{sessionId}) when you need the latest known status
Test vs Live: in Test webhooks are generated by our own simulator and reflect only the simulation you triggered. There are no chargebacks or reversals in Test.
General format
All webhooks arrive as POST to your URL with this format:
Headers
| Header | Value |
|---|---|
Content-Type | application/json |
webcode | Webcode associated with the payment (e.g. SHOP01) |
Body
{
"event_code": "AUTHORISATION",
"data": {
...event-specific fields...
}
}Expected response
Your endpoint must respond with HTTP 200. The response body is not evaluated — the status code alone is enough.
If your endpoint does not respond with HTTP 200, the webhook will be retried automatically with exponential backoff.
---
Event types
AUTHORISATION — Payment completed (online or POS)
Sent when a payment (online or POS) is authorized successfully or fails.
{
"event_code": "AUTHORISATION",
"data": {
"amount": 15000,
"currency": "EUR",
"paymentReference": "SHOP01-PXT-17195000005678",
"paymentStatus": "AUTHORISED",
"description": "Pedido #12345 - Servicio premium",
"customReference": "ORD-2026-12345",
"paymentSource": "external_api"
}
}| Field | Always present | Description |
|---|---|---|
amount | Yes | Amount in cents |
currency | Yes | ISO 4217 code |
paymentReference | Yes | Payment reference |
paymentStatus | Yes | AUTHORISED if successful, FAILED if failed |
description | Yes | Payment description |
customReference | No | Your internal reference (if provided when creating session/payment) |
paymentSource | No | Payment origin (e.g. external_api) |
failedReason | No | Failure reason (only if payment failed) |
How to identify payment type by reference:
| Reference infix | Type | Environment |
|---|---|---|
-PXT- | Online payment | Live |
-PXTT- | Online payment | Test |
-POSX- | POS payment | Live |
-POSXT- | POS payment | Test |
Successful POS payment example:
{
"event_code": "AUTHORISATION",
"data": {
"amount": 8500,
"currency": "EUR",
"paymentReference": "SHOP01-POSX-17195300001234",
"paymentStatus": "AUTHORISED",
"description": "Venta mostrador #205",
"customReference": "VENTA-205-20260628",
"paymentSource": "external_api"
}
}Failed payment example (declined):
{
"event_code": "AUTHORISATION",
"data": {
"amount": 15000,
"currency": "EUR",
"paymentReference": "SHOP01-PXT-17195000005678",
"paymentStatus": "FAILED",
"description": "Pedido #12345",
"failedReason": "Refused",
"paymentSource": "external_api"
}
}Important: Webhooks are sent for both successful and failed payments. Always check the
paymentStatusfield to determine the result.
---
REFUND — Refund processed
Sent when a refund completes (success or failure) at the processor.
{
"event_code": "REFUND",
"data": {
"amount": 5000,
"currency": "EUR",
"paymentReference": "SHOP01-PXT-17195000005678",
"refundReason": "CUSTOMER REQUEST",
"refundReference": "SHOP01-RFX-17195234567890",
"status": "success",
"customReference": "ORD-2026-12345",
"paymentSource": "external_api"
}
}| Field | Always present | Description |
|---|---|---|
amount | Yes | Refunded amount in cents |
currency | Yes | ISO 4217 code |
paymentReference | Yes | Original payment reference |
refundReason | Yes | Refund reason (CUSTOMER REQUEST, FRAUD, RETURN, DUPLICATE, OTHER) |
refundReference | Yes | This refund operation reference |
status | Yes | success or failed |
customReference | No | Your internal reference |
paymentSource | No | Payment origin |
failedReason | No | Failure reason (only if status: "failed") |
Failed refund example:
{
"event_code": "REFUND",
"data": {
"amount": 5000,
"currency": "EUR",
"paymentReference": "SHOP01-PXT-17195000005678",
"refundReason": "CUSTOMER REQUEST",
"refundReference": "SHOP01-RFX-17195234567890",
"status": "failed",
"failedReason": "Insufficient balance"
}
}---
PREAUTHORISATION — Capture, cancellation, or extension result
Sent when an operation on a pre-authorization completes. The action field indicates the operation type.
Capture completed
{
"event_code": "PREAUTHORISATION",
"data": {
"paymentReference": "SHOP01-PXT-17195123456789",
"action": "capture",
"result": "success",
"amount": 45000,
"currency": "EUR",
"customReference": "ORD-2026-67890",
"paymentSource": "external_api"
}
}Cancellation completed
{
"event_code": "PREAUTHORISATION",
"data": {
"paymentReference": "SHOP01-PXT-17195123456789",
"action": "cancellation",
"result": "success",
"customReference": "ORD-2026-67890",
"paymentSource": "external_api"
}
}Extension completed
{
"event_code": "PREAUTHORISATION",
"data": {
"paymentReference": "SHOP01-PXT-17195123456789",
"action": "extend",
"result": "success",
"expirationDate": "2026-08-15T00:00:00Z",
"customReference": "ORD-2026-67890",
"paymentSource": "external_api"
}
}| Field | Always present | Description |
|---|---|---|
paymentReference | Yes | Pre-authorized payment reference |
action | Yes | capture, cancellation, or extend |
result | Yes | success or failure |
amount | Only capture | Captured amount in cents |
currency | Only capture | ISO 4217 code |
expirationDate | Only successful extend | New expiration date |
customReference | No | Your internal reference |
paymentSource | No | Payment origin |
failedReason | No | Failure reason (only if result: "failure") |
Failed capture example (amount exceeds authorized):
{
"event_code": "PREAUTHORISATION",
"data": {
"paymentReference": "SHOP01-PXT-17195123456789",
"action": "capture",
"result": "failure",
"failedReason": "Payment already captured, cannot capture again"
}
}Note on modification flow: All modifications (refund, capture, cancel, extend) are asynchronous. The immediate API response returns status
received, indicating the request has been accepted for processing. The final result (successorfailed) arrives later via webhook. The integrator must not consider the operation complete until receiving the confirmation webhook.
---
CHARGEBACK — Chargeback
Sent when a cardholder initiates a chargeback against a payment. Chargebacks are processed by the card issuer and may take days or weeks to resolve.
{
"event_code": "CHARGEBACK",
"data": {
"paymentReference": "SHOP01-PXT-17195123456789",
"chargebackReference": "SHOP01-CHB-17195123456789",
"action": "chargeback",
"result": "success",
"amount": 15000,
"currency": "EUR",
"reason": "Fraudulent transaction",
"chargebackReasonCode": "10.4",
"chargebackSchemeCode": "VISA_10_4",
"defensePeriodEndsAt": "2026-06-15",
"defendable": true,
"disputeStatus": "Undefended",
"paymentSource": "external_api"
}
}| Field | Always present | Description |
|---|---|---|
paymentReference | Yes | Original payment reference |
chargebackReference | Yes | Unique chargeback reference |
action | Yes | Always chargeback |
result | Yes | success or failure |
amount | Yes | Amount in cents |
currency | Yes | ISO 4217 currency |
reason | Yes | Chargeback reason |
chargebackReasonCode | No | Card scheme reason code |
chargebackSchemeCode | No | Scheme code (Visa, Mastercard, etc.) |
defensePeriodEndsAt | No | Deadline to defend the chargeback |
defendable | No | true if you can dispute the chargeback |
disputeStatus | No | Dispute status (Undefended, Defended, etc.) |
failedReason | No | Reason if result is failure |
paymentSource | No | Payment origin (external_api, etc.) |
---
NOTIFICATION_OF_CHARGEBACK — Chargeback advance notice
Advance notice that a chargeback may be incoming. Not all processors send it. Lets you prepare before the formal chargeback is processed.
{
"event_code": "NOTIFICATION_OF_CHARGEBACK",
"data": {
"paymentReference": "SHOP01-PXT-17195123456789",
"chargebackReference": "SHOP01-CHB-17195123456789",
"action": "notification_of_chargeback",
"result": "success",
"amount": 15000,
"currency": "EUR",
"reason": "Cardholder disputes transaction",
"chargebackReasonCode": "10.4",
"chargebackSchemeCode": "VISA_10_4",
"defensePeriodEndsAt": "2026-06-15",
"defendable": true,
"disputeStatus": "Undefended",
"autoDefended": false,
"arn": "74027600000012345678901",
"paymentSource": "external_api"
}
}Contains the same fields as CHARGEBACK plus two optional additional fields:
| Field | Description |
|---|---|
autoDefended | true if the processor automatically defended the chargeback |
arn | Acquirer Reference Number — acquirer transaction identifier |
---
Webhooks in the Test environment
In the Test environment, webhooks do not come from the real payment processor. They are generated automatically by the Avirato Payments backend immediately after the simulated operation completes.
Differences from Live
| Aspect | Live | Test |
|---|---|---|
| Sender | Payment processor | Avirato Payments backend |
| When it arrives | Seconds to minutes after the operation | 1-3 seconds after the HTTP response |
| Format | Identical | Identical |
| Fields | Identical | Identical |
| Automatic retries | Yes (exponential backoff, up to ~4 days) | No: simulator makes a single delivery |
| Manual resend | Yes, from dashboard | Yes, from dashboard (same button) |
| HTTP success criterion | HTTP 200 | HTTP 200 |
What you should know
- The format is exactly the same: You can develop your webhook processing logic in Test and it will work in Live without changes
- They arrive for successful AND failed payments: In both Live and Test you will receive webhooks when a payment is declined or an operation fails
- Independent URLs per environment: Test webhooks are sent to the URL configured for Test, and Live to the Live URL. Configure both from the dashboard webhook panel
- References use Test suffixes:
paymentReferenceandoperationReferencewill contain test infixes (-PXTT-,-POSXT-,-RFXT-, etc.) - Simple success criterion: In both Live and Test, your endpoint only needs to respond HTTP 200 for the webhook to be marked delivered. Any other status is considered failure and triggers retries (in Live) or remains failed in the dashboard (in Test).
- Manual resend available in Test and Live: From the dashboard webhook panel you can manually resend a specific webhook. In both Test and Live, resend uses the URL currently configured for that environment, not the URL used on the original delivery. If you resend an already delivered webhook, the dashboard will ask for explicit confirmation to avoid accidental duplicates.
Test webhook example (successful online payment)
{
"event_code": "AUTHORISATION",
"data": {
"amount": 15000,
"currency": "EUR",
"paymentReference": "SHOP01-PXTT-17195000005678",
"paymentStatus": "AUTHORISED",
"description": "Pedido test #12345",
"customReference": "TEST-ORD-12345",
"paymentSource": "external_api"
}
}Test webhook example (declined POS)
{
"event_code": "AUTHORISATION",
"data": {
"amount": 10077,
"currency": "EUR",
"paymentReference": "SHOP01-POSXT-17195300001234",
"paymentStatus": "FAILED",
"description": "Venta test #100",
"failedReason": "Refused",
"paymentSource": "external_api"
}
}---
Quick reference
| Operation | event_code | action (if applicable) | How to identify External API |
|---|---|---|---|
| Online payment completed | AUTHORISATION | — | paymentReference contains -PXT- (Live) or -PXTT- (Test) |
| POS payment completed | AUTHORISATION | — | paymentReference contains -POSX- (Live) or -POSXT- (Test) |
| Refund processed | REFUND | — | refundReference contains -RFX- (Live) or -RFXT- (Test) |
| Capture processed | PREAUTHORISATION | capture | Operation on External API payment |
| Cancellation processed | PREAUTHORISATION | cancellation | Operation on External API payment |
| Extension processed | PREAUTHORISATION | extend | Operation on External API payment |
| Chargeback received | CHARGEBACK | — | Chargeback initiated by cardholder |
| Chargeback notification | NOTIFICATION_OF_CHARGEBACK | — | Advance notice of possible chargeback |
---
Best practices
Idempotency on your endpoint
Your webhook endpoint may receive the same notification more than once (due to retries or processor duplicates). Make sure processing the same webhook twice does not cause problems: use paymentReference or refundReference as a deduplication key.
Fast response
Respond to the webhook as soon as possible (ideally in under 5 seconds). If you need heavy processing, queue the work and respond immediately with HTTP 200.
Do not rely on a single channel
Use webhooks as the primary channel, but also implement:
- Post-redirect verification: When the customer returns to
urlOk, queryGET /payment/session/{id}to confirm - Safety polling: A periodic process that queries old
pendingsessions to detect completed payments you have not processed