Overview
The Transaction Processed Webhook is triggered when a transaction's outcome changes, for example when it is approved, declined or cancelled, or when its session expires.\ This webhook enables you to synchronize your system with the transaction outcome without polling.
When It Is Triggered
Qore Payments will send the Transaction Processed Webhook when:
- A transaction is approved or declined by the payment provider.
- A transaction is cancelled.
- A session expires before the customer pays.
- A capture, refund, void or credit is approved or declined.
You see only the latest attempt of an order: a decline that can still be retried may reach you up to 15 minutes later, or not at all once a retry has its own outcome (see Delivery and Retry Policy).
A request rejected as invalid (status INVALID) triggers no Transaction Processed event: the API answer is the only outcome. A transaction-svc:transaction:created event may already have been sent for it.
Supported Events
The Transaction Processed Webhook is triggered for the following events:
| Event Value | Description |
|---|---|
transaction-svc:transaction:authorized |
Any transaction approved (general event) |
transaction-svc:transaction:authorize:authorized |
Authorization transaction successful |
transaction-svc:transaction:purchase:approved |
Purchase transaction approved |
transaction-svc:transaction:authorize:declined |
Authorization declined |
transaction-svc:transaction:purchase:declined |
Purchase declined |
transaction-svc:transaction:authorize:pending |
Authorization pending (not sent today, see below) |
transaction-svc:transaction:purchase:pending |
Purchase pending (not sent today, see below) |
transaction-svc:transaction:pending |
General transaction pending (not sent today, see below) |
transaction-svc:transaction:declined |
General transaction declined |
transaction-svc:transaction:cancelled |
General transaction cancelled |
transaction-svc:transaction:authorize:cancelled |
Authorization cancelled |
transaction-svc:transaction:purchase:cancelled |
Purchase cancelled |
transaction-svc:transaction:session-expired |
The session expired before the customer paid |
transaction-svc:transaction:management:declined |
Management operation declined (also the only declined event of a CREDIT) |
transaction-svc:transaction:management:approved |
Management operation approved (also the only approved event of a CREDIT) |
transaction-svc:transaction:management:refund:declined |
Refund operation declined |
transaction-svc:transaction:management:refund:approved |
Refund operation approved |
transaction-svc:transaction:management:void:declined |
Void operation declined |
transaction-svc:transaction:management:void:approved |
Void operation approved |
transaction-svc:transaction:management:capture:declined |
Capture operation declined |
transaction-svc:transaction:management:capture:approved |
Capture operation approved |
A general event and a type-specific event fire for the same change. For example, an approved purchase fires both transaction-svc:transaction:authorized and transaction-svc:transaction:purchase:approved, and an approved refund fires both transaction-svc:transaction:management:approved and transaction-svc:transaction:management:refund:approved. Subscribe to the general events or to the type-specific ones: an endpoint subscribed to both receives two deliveries for one change. A CREDIT fires only the general transaction-svc:transaction:management:approved / transaction-svc:transaction:management:declined events, so subscribe to those if you use credits.
The three pending events can be subscribed to, but Qore Payments does not send them for real transactions today. A transaction that goes pending sends no webhook until it reaches a final status; you then receive that status's events (approved, declined, cancelled or session expired). Only a test delivery from the merchant portal sends a pending event.
transaction-svc:transaction:created, sent when a transaction is created, belongs to a separate event service. It carries the same payload and is signed the same way, and shows the transaction as it is when the webhook is built. For a Hosted Payment Page or Embedded Fields session, processing_code and response_message are usually null, because the customer hasn't paid yet. After a Direct API authorize, they already carry the outcome.
Request Details
| Detail | Value |
|---|---|
| HTTP Method | POST |
| Content-Type | application/json |
| Authorization Header | Bearer <JWT> |
| Timeout | 15 seconds per attempt |
| Retries | 3, with exponential backoff (about 1, 2 and 4 seconds, each varying by up to 50%). A slow endpoint can receive more; see Delivery and Retry Policy |
| After the last retry | No further attempts |
| Signature Validation | JWT signed using Merchant Private Key |
Headers
| Header | Description |
|---|---|
Authorization |
Bearer token containing a signed JWT with SHA-512 digest validation |
Content-Type |
application/json |
Accept |
text/plain |
X-Webhook-Test |
true, only on a test delivery sent from the portal |
Example:
POST /your/webhook/endpoint HTTP/1.1
Host: your-server.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
Accept: text/plain
Payload Schema
Here’s the JSON structure you can expect:
{
"event": "transaction-svc:transaction:authorized",
"payload": {
"id": "KlLQpgjqCmsmogHfNjuS",
"merchant_id": "eyCnTugDzIMADoQLTUJF",
"terminal_id": "eyCnTugDzIMADoQLTUJF",
"order_id": "auDCeVomqaFvBLxStpuO",
"payment_link_id": null,
"reference": "oONQfOdgIF",
"description": "kzCPRVsEbY",
"currency": "EUR",
"amount": 100,
"transaction_type": "PURCHASE",
"processing_code": "0000",
"response_message": "TRX_STATUS_APPROVED",
"status": "APPROVED",
"metadata": {
"response_code": "TRX_STATUS_APPROVED",
"simulated_flow": "VERSIONING_FINGERPRINT_N_FRICTIONLESS",
"additionalProp1": "string",
"additionalProp2": "string"
},
"created_at": "2025-04-28T12:42:26.976811Z",
"updated_at": "2025-04-28T12:42:27.144339Z",
"approval_code": "bfzLph",
"reference_number": "",
"parent_id": null,
"systan": null,
"transaction_initiated_by": "CUSTOMER",
"authentication_level": null,
"payment_provider_response_code": null,
"payment_provider_response_message": null,
"normalized_amount": 100,
"customer_id": null,
"payment_method_method": "41111111-******-1111",
"payment_method_hash": null,
"payment_method_type": "card",
"payment_method_brand": "VISA",
"payment_method_masked": "41111111-******-1111",
"payment_method_token": null,
"bin_issuer_country": "US",
"customer_first_name": "Carey",
"customer_last_name": "Kuhn",
"customer_address": "877 Don Centers",
"customer_city": "Port Dalton",
"customer_country": "Singapore",
"customer_postal_code": "43000",
"customer_email": "barrett.rolfson@hotmail.com",
"customer_phone_number": "+1 985-502-6937 x8449",
"authentication_status": "AUTHENTICATED",
"payment_method_expiry": "2027-12-31",
"number_of_instalments": null
}
}
In the example, number_of_instalments is null: the field is null unless the cardholder chose instalments. See Instalments.
Field Descriptions
| Field | Type | Description |
|---|---|---|
event |
string |
One of transaction processed events |
payload.id |
string |
Unique transaction identifier |
payload.merchant_id |
string |
Merchant identifier |
payload.terminal_id |
string |
Terminal identifier |
payload.order_id |
string |
Order ID associated with transaction |
payload.payment_link_id |
string |
Payment Link ID (nullable) |
payload.reference |
string |
Merchant-provided reference |
payload.description |
string |
Transaction description |
payload.currency |
string |
Currency code (ISO 4217) |
payload.amount |
number (integer) |
Amount in minor units (e.g., cents) |
payload.transaction_type |
string |
Transaction type, uppercase (e.g., PURCHASE, AUTHORIZE, REFUND) |
payload.processing_code |
string or null |
Normalized Qore Payments processing code, e.g. 0000 (see Handling declines). Usually null on transaction-svc:transaction:created for a session the customer hasn't paid yet |
payload.response_message |
string or null |
The processing code's name, e.g. TRX_STATUS_APPROVED for 0000 or RISK_FAILED_FRAUD for 4101. If provider details are enabled for your account, it can carry the provider's own message instead |
payload.status |
string |
Transaction status, uppercase (APPROVED, DECLINED, PENDING, CANCELED, SESSION_EXPIRED, etc.) |
payload.metadata |
object |
Custom merchant metadata (optional) |
payload.created_at |
string (ISO-8601 datetime) |
When the transaction was created |
payload.updated_at |
string (ISO-8601 datetime) |
When the transaction was last updated |
payload.payment_provider_response_code |
string or null |
null on a merchant, shop or terminal endpoint, unless provider details are enabled for your account |
payload.payment_provider_response_message |
string or null |
null on a merchant, shop or terminal endpoint, unless provider details are enabled for your account or Qore Payments has set the endpoint to include the provider's message |
payload.bin_issuer_country |
string or null |
Country of the card issuer, from the card BIN |
payload.payment_method_expiry |
string (ISO-8601 date) or null |
Payment method expiry date (last day of expiry month, e.g., 2027-12-31) |
payload.number_of_instalments |
integer or null |
Number of instalments the cardholder chose in the card form; null when the payment was taken in full. Always present. If a Hosted Payment Page session sets options.number_of_instalments, that value is reported when the cardholder picks none. See Instalments. |
Fields without a value are sent as null, not left out. Don't branch on payment_provider_response_code or payment_provider_response_message: branch on status, and read why a transaction failed from processing_code.
JSON Schema
Here is the JSON Schema for validating the Transaction Processed Webhook payload (download and view):
transaction-processed-payload.json
New fields can be added to the payload, as number_of_instalments was. The schema accepts fields it doesn't list, so don't reject a webhook because it carries a field you don't know.
Important Notes
- Always verify the JWT signature using the appropriate merchant public key.
- Always validate that the SHA-512 digest of the request body matches the value inside the JWT.
- Qore Payments retries a failed delivery 3 times with exponential backoff, then drops the event. See Delivery and Retry Policy.
- Duplicate webhook deliveries are possible: ensure your webhook handlers are idempotent.