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

# Session Status Webhook

> When a Guaranteed EFT session reaches its final outcome, Flinks sends a `SessionSetupStatusChanged` event to your HTTPS endpoint. Use it to update your order or payment as soon as the result is known, instead of polling the session status endpoint.

## Before you start

You need an HTTPS endpoint registered with Flinks and the webhook secret we give you. Follow [Client Setup](/guides/pay/geft/webhooks/client-setup) to register your URL and verify each event's signature before you build your handler. Use separate endpoints and secrets for sandbox and production.

## How it fits in the session lifecycle?

A session is created when you start a Guaranteed EFT flow and moves through these states: `Initiated`, `InProgress`, then one final state: `Completed`, `Failed`, `Canceled` or `Expired`.

Flinks sends a webhook only for the three final states `Completed`, `Failed` and `Canceled`. `Initiated`, `InProgress` and `Expired` do not trigger an event. If you need to know about them, read them from the session status endpoint (`GET /api/v2/sessions/{sessionId}/status`).

## At a glance

| | |
| :- | :- |
| **Sent when** | A session becomes `Completed`, `Failed` or `Canceled` |
| **Not sent for** | Sessions still in progress or expired |
| **Retries** | **None**. Each event is sent once. An error response or a timeout loses it |
| **Event id** | None. Deduplicate on `sessionId` |
| **Fallback** | `GET /api/v2/sessions/{sessionId}/status` returns the same fields |

## Handle the event

```json theme={null}
{
  "when": "2026-10-08T14:03:12.4815162Z",
  "payload": {
    "sessionId": "3f6c1a2e-9b7d-4e1f-a2c3-5d8e9f0a1b2c",
    "amount": 125.00,
    "referenceId": "order-1",
    "status": "Completed",
    "statusDetails": "EFT0301"
  },
  "type": "SessionSetupStatusChanged"
}
```

| Field | Type | Notes |
| :- | :- | :- |
| `type` | string | Always `SessionSetupStatusChanged` |
| `when` | ISO 8601, UTC | When the event was sent |
| `payload.sessionId` | uuid | The id returned when you created the session |
| `payload.amount` | number or null | `null` when the session ended before an amount was set |
| `payload.referenceId` | string | The reference you passed at session creation, empty if none |
| `payload.status` | string | `Completed`, `Failed` or `Canceled` |
| `payload.statusDetails` | string | Reason code, see [Status codes](#status-codes). Can be empty |

## Status codes

| Status | statusDetails | Meaning |
| :- | :- | :- |
| `Completed` | `EFT0301` | Transaction scheduled, session fully completed |
| `Failed` | `EFT0401` | Login failed: invalid financial institution credentials |
| `Failed` | `EFT0402` | Eligibility failed: no Guaranteed EFT offered |
| `Failed` | `EFT0403` | Identity failed: invalid user information verification |
| `Failed` | `EFT0404` | Insufficient balance: account has insufficient funds to complete transaction |
| `Failed` | `EFT0405` | Unable to process: the payment couldn't be completed. No funds have been taken |
| `Canceled` | `EFT0501` | Session canceled by API request |

## Reconcile

Because events are not retried, call `GET /api/v2/sessions/{sessionId}/status` for any session you expected an outcome for but did not receive one. It returns the same fields.

## Best practices

* **Acknowledge first.** Return `200` as soon as the signature checks out, then process asynchronously.
* **Deduplicate on `sessionId`.** Events carry no id of their own.
* **Reconcile.** Because events are not retried, call the status endpoint for any session you expected an outcome for but did not receive one.
* **Use HTTPS only.**
