Webhooks
Paytalya sends a webhook to your endpoint as a payment or refund status changes. The payload is thin: only the event type + paymentCode. A webhook is a trigger, not the authoritative result; you fetch the detail and the final status via GET /v1/payments.
Event types
Section titled “Event types”| Event type | When it is sent |
|---|---|
payment.captured | Capture approved (payment captured). |
payment.failed | 3D or capture was rejected (payment failed). |
payment.expired | Payment expired / 3D abandoned (payment expired). |
refund.approved | A refund or void was approved. |
refund.declined | A refund or void was declined. |
Raw bank events (authorization steps, intermediate states) are not sent as webhooks. For the full list of payment statuses, see Payment Lifecycle.
Body (thin payload)
Section titled “Body (thin payload)”{ "id": "whd_a1b2c3", "type": "payment.captured", "paymentCode": "pay_7Hq2bL", "occurredAt": "2026-06-14T12:05:11Z"}| Field | Description |
|---|---|
id | Delivery id. Used for idempotency. |
type | Event type (table above). |
paymentCode | The related payment’s code; use it to query the detail. |
occurredAt | The instant the event occurred (UTC, ISO 8601). |
The body does not carry the authoritative result or amount; this is intentional. It contains no raw bank response, card data, or any other sensitive field. Always query the payment to make a decision.
Headers
Section titled “Headers”Every delivery carries three signature headers:
| Header | Description |
|---|---|
X-Paytalya-Signature | sha256=<hex>: the HMAC-SHA256 signature of the raw request body. |
X-Paytalya-Timestamp | The epoch seconds of the instant the event occurred (the same instant as occurredAt in the body, in seconds form). Provided for optional replay hardening; it is not part of the signature. You may reject requests older than 5 minutes (replay protection), but that check is independent of the signature. |
X-Paytalya-Delivery-Id | Delivery id (same as id in the body). The same delivery may arrive again; use it for idempotency. |
The secret used for the signature is a webhook secret separate from the API key and is provided to you during onboarding. Never log this secret, send it to the client, or store it in your repository.
Signature verification
Section titled “Signature verification”The signature is computed over the raw request body only; the timestamp is not included in the signed value, and there is no concatenation. Verify it with a constant-time comparison (against timing attacks). Language-agnostic pseudocode:
rawBody = exact bytes received on the request
expected = "sha256=" + lowercase_hex( HMAC_SHA256(webhookSecret, rawBody) )
# 1) signature must match (constant-time comparison): over the raw body onlyif not constantTimeEquals(expected, header["X-Paytalya-Signature"]): return 400
# 2) (optional) replay hardening: reject events that are too old (INDEPENDENT of the signature)timestamp = header["X-Paytalya-Timestamp"]if abs(now() - timestamp) > 5 minutes: return 400
# signature valid → return 2xx, then query the payment with GETreturn 200Endpoint requirements
Section titled “Endpoint requirements”- Return
2xxfast. Your endpoint must respondHTTP 2xxwithin 5 seconds; otherwise the delivery is treated as a timeout and retried. Do heavy work (querying, updating the order) asynchronously after you respond. - Verify the signature. Do not trust the body before verifying the signature and timestamp with the steps above.
- Be idempotent. The same
X-Paytalya-Delivery-Idmay arrive more than once (the delivery-id is stable across retries). Your processing logic must be resilient to receiving the same delivery twice. - Use it as a trigger. When a notification arrives, query the payment detail via
GET /v1/paymentsand base your decision on the query result.
Retries and delivery
Section titled “Retries and delivery”-
Success:
HTTP 2xx. The delivery is considered complete. -
Permanent failure: a
4xxclient error → not retried. Make sure your endpoint returns2xxfor valid deliveries. -
Retry:
5xxresponses and timeouts → retried with exponential backoff:30s → 2m → 10m → 1h → 6hIf no success is received within 24 hours of the first delivery, the delivery is permanently dropped.
-
No ordering guarantee: Events may not arrive in the order they were sent. Determine status from the
GET /v1/paymentsresult, not from the webhook type.
Configuration
Section titled “Configuration”Your webhook URL and secret are configured on your account during onboarding. If no webhook URL is configured, no notifications are sent; in that case, track payment statuses solely via GET /v1/payments.