> ## Documentation Index
> Fetch the complete documentation index at: https://safepay.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhook events and payloads

> Understand the webhook event envelope and inspect payment, payout, and refund examples.

Every webhook request body is a JSON event envelope. Route the event using `X-SFPY-EVENT-TYPE` or `webhookEventType`, deduplicate it using `X-SFPY-EVENT-ID` or `identifier`, and then inspect `subject.payment` for the affected resource.

## Event envelope

| Field              | Type   | Description                                                                                                                           |
| ------------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------- |
| `source`           | string | Event producer. Currently `SAFEPAY_RAAST`.                                                                                            |
| `kind`             | string | Broad category: `PAYMENT_EVENT`, `REFUND_EVENT`, or `SETTLEMENT_EVENT`.                                                               |
| `code`             | string | Raast event code corresponding to the state change.                                                                                   |
| `version`          | string | Payload version. Currently `V1_0_0`.                                                                                                  |
| `timestamp`        | string | UTC RFC 3339 time at which the event was created. This differs from `X-SFPY-TIMESTAMP`, which is generated for each delivery attempt. |
| `identifier`       | string | Unique event ID; matches `X-SFPY-EVENT-ID`.                                                                                           |
| `partnerId`        | string | Partner identifier when the aggregator belongs to a partner. It can be empty.                                                         |
| `aggregatorId`     | string | Aggregator identifier; matches `X-SFPY-AGGREGATOR-ID`.                                                                                |
| `subject`          | object | Resource snapshot associated with the event. Webhook events currently carry it in `subject.payment`.                                  |
| `destinations`     | array  | Delivery-routing metadata. Do not use it for business logic.                                                                          |
| `raastChannel`     | string | Internal channel metadata. It can be empty.                                                                                           |
| `webhookEventType` | string | Public event name; matches `X-SFPY-EVENT-TYPE`.                                                                                       |

JSON property names use lower camel case. Protobuf `int64` values, including payment amounts and internal numeric IDs, are serialized as JSON strings. Timestamps use RFC 3339 and enum values use their string names.

<Callout type="info">
  Treat payload evolution as additive. Ignore fields and enum values you do not recognize, and use `webhookEventType` rather than relying only on `kind` or `code`.
</Callout>

## Complete `payment.completed` example

The following is a representative complete body generated by the current `V1_0_0` serializer. Empty strings, empty arrays, and `null` nested resources are included because the serializer emits unpopulated fields.

```json theme={null}
{
  "source": "SAFEPAY_RAAST",
  "kind": "PAYMENT_EVENT",
  "code": "PAYMENT_CAPTURED",
  "version": "V1_0_0",
  "timestamp": "2026-09-14T10:15:30.123456Z",
  "identifier": "txnlog_5246fdcc-6c86-49bb-9da2-aa38262e0291",
  "partnerId": "partner_12345678-1234-1234-1234-123456789012",
  "aggregatorId": "agg_2288490a-2176-4de5-b373-0ffb6f8e2e6e",
  "subject": {
    "payment": {
      "id": "0",
      "token": "pm_12345678-1234-1234-1234-123456789012",
      "aggregatorId": "agg_2288490a-2176-4de5-b373-0ffb6f8e2e6e",
      "aggregatorMerchantId": "am_12345678-1234-1234-1234-123456789012",
      "qrCodeId": "",
      "orderId": "order-1042",
      "type": "RTP_NOW",
      "amount": "250000",
      "status": "P_CAPTURED",
      "uetr": "",
      "traceReference": "",
      "msgId": "",
      "instrId": "",
      "endToEndId": "",
      "pmtInfId": "",
      "requestId": "8e3df3ac-2607-4f3a-8a69-67e4efc284b3",
      "msgCreatedAt": "",
      "expiresAt": null,
      "createdAt": "2026-09-14T10:14:55Z",
      "updatedAt": "2026-09-14T10:15:30Z",
      "txnLogs": [],
      "paymentParties": [],
      "qrCode": null,
      "aggregatorMerchant": null,
      "refunds": [],
      "settlement": null,
      "paymentAuthorization": null,
      "aggregator": null,
      "isoOrderReference": ""
    }
  },
  "destinations": [],
  "raastChannel": "",
  "webhookEventType": "payment.completed"
}
```

The exact `subject.payment` snapshot can contain populated transaction logs, payment parties, QR details, merchant details, refunds, settlement details, authorization details, and aggregator details. Persist the entire event, but select only the fields your integration needs.

## Refund event example

Refund lifecycle events use `kind: REFUND_EVENT` and include the payment plus its refund records. This focused example omits unrelated empty payment fields for readability; the delivered body uses the same complete envelope shown above.

```json theme={null}
{
  "source": "SAFEPAY_RAAST",
  "kind": "REFUND_EVENT",
  "code": "REFUND_COMPLETED",
  "version": "V1_0_0",
  "timestamp": "2026-09-14T11:04:22.834Z",
  "identifier": "txnlog_1a574d66-9bdf-48ae-a858-802951c46705",
  "partnerId": "partner_12345678-1234-1234-1234-123456789012",
  "aggregatorId": "agg_2288490a-2176-4de5-b373-0ffb6f8e2e6e",
  "subject": {
    "payment": {
      "token": "pm_12345678-1234-1234-1234-123456789012",
      "aggregatorId": "agg_2288490a-2176-4de5-b373-0ffb6f8e2e6e",
      "orderId": "order-1042",
      "type": "RTP_NOW",
      "amount": "250000",
      "status": "P_REFUNDED",
      "refunds": [
        {
          "id": "0",
          "token": "rfd_12345678-1234-1234-1234-123456789012",
          "paymentId": "pm_12345678-1234-1234-1234-123456789012",
          "amount": "250000",
          "type": "FULL_REFUND",
          "reason": "TechnicalProblem",
          "addtlInfo": "Customer refund",
          "status": "R_COMPLETED",
          "traceReference": "refund-trace-1042",
          "msgId": "refund-message-1042",
          "returnId": "return-1042",
          "requestId": "b27d5c04-6ad4-4fb6-b9fa-2ffc81ac5db8",
          "msgCreatedAt": "2026-09-14T11:03:58Z",
          "settleAt": "2026-09-14T11:04:20Z",
          "createdAt": "2026-09-14T11:03:57Z",
          "updatedAt": "2026-09-14T11:04:22Z"
        }
      ]
    }
  },
  "destinations": [],
  "raastChannel": "",
  "webhookEventType": "refund.completed"
}
```

Payment-level events `payment.refunded` and `payment.refund_partial` can describe the resulting aggregate payment status. Events in the `refund.*` family describe the individual refund lifecycle. Make refund processing idempotent using the event `identifier` and identify the refund by `subject.payment.refunds[].token`.

## Payout event example

Payouts use the `payment.*` event family. Identify a payout through `subject.payment.type`; there are no `payout.*` webhook names.

```json theme={null}
{
  "source": "SAFEPAY_RAAST",
  "kind": "PAYMENT_EVENT",
  "code": "PAYMENT_SETTLED",
  "version": "V1_0_0",
  "timestamp": "2026-09-14T12:25:40Z",
  "identifier": "txnlog_d55ccbd4-f770-4228-869c-adc3afe31dc7",
  "partnerId": "partner_12345678-1234-1234-1234-123456789012",
  "aggregatorId": "agg_2288490a-2176-4de5-b373-0ffb6f8e2e6e",
  "subject": {
    "payment": {
      "token": "pm_eea10c61-0b79-44c9-b218-57f14692da41",
      "aggregatorId": "agg_2288490a-2176-4de5-b373-0ffb6f8e2e6e",
      "aggregatorMerchantId": "",
      "orderId": "payout-7781",
      "type": "PAYOUT",
      "amount": "500000",
      "status": "P_SETTLED",
      "requestId": "8b76454c-30b0-412f-98b2-da72df9c0f9c",
      "traceReference": "payout-trace-7781"
    }
  },
  "destinations": [],
  "raastChannel": "",
  "webhookEventType": "payment.settled"
}
```

## Failed events

For `payment.failed`, `payment.rejected`, or `refund.failed`, use the resource snapshot and its transaction logs to determine the latest known state. Do not confuse a business failure inside the event with `last_error` from the Webhook Delivery API: `last_error` describes Safepay’s HTTP delivery attempt, such as `status 422`, not why the payment or refund failed.

## Match headers to the body

Before processing, verify that the routing headers agree with the signed body:

| Header                 | Body field         |
| ---------------------- | ------------------ |
| `X-SFPY-EVENT-ID`      | `identifier`       |
| `X-SFPY-EVENT-TYPE`    | `webhookEventType` |
| `X-SFPY-AGGREGATOR-ID` | `aggregatorId`     |

The HMAC covers the body, not these routing headers individually. Verify the body signature first, then reject the request if a routing header does not match its signed body field or your expected aggregator and subscription.

## See also

* [Webhooks delivery](/guides/webhooks-delivery)
* [Webhooks concept](/concepts/webhooks)
* [Payment lifecycle](/guides/payment-lifecycle)
* [Refunds](/concepts/refunds)
* [Find Webhook Delivery](/api-reference/webhooks/find-webhook-delivery)
