Webhook Events
The Bank Accounts product sends two event families:
| Event | When |
|---|---|
bank_account.transaction.received | A new transaction is read on your connected accounts. |
bank_transfer.created, bank_transfer.completed, bank_transfer.failed, bank_transfer.returned | A money transfer is created and settled. → Transfer events |
Delivery, signature verification and retries are the same for all products: Webhooks. The first part of this page covers the transaction event.
When it is sent
| Case | Event |
|---|---|
A transaction seen for the first time in a regular read — incoming (CREDIT) or outgoing (DEBIT) | Sent, one event per transaction |
| Past transactions read back when the connection is first set up | Not sent |
| Transactions found when past data is read again | Not sent |
| A transaction already read arriving from the bank again | Not sent; the same transaction is not recorded twice |
| A transaction held for verification that later becomes visible in the list | Not sent |
Transactions without an event still appear on the Transactions endpoint. To get all transactions, use the webhook together with the list endpoint (see below).
Sample delivery
{
"id": "00000000-0000-0000-0000-00000000e001",
"type": "bank_account.transaction.received",
"organizationId": "00000000-0000-0000-0000-00000000000a",
"productCode": "BANK_CONNECT",
"environment": "LIVE",
"eventVersion": 1,
"schemaVersion": "v1",
"createdAt": "2026-01-01T07:05:01.000Z",
"data": {
"transactionId": "00000000-0000-0000-0000-000000001000",
"accountId": "00000000-0000-0000-0000-000000000100",
"connectionId": "00000000-0000-0000-0000-000000000010",
"bankEftCode": "0000",
"bankName": "ÖRNEK BANKA",
"direction": "CREDIT",
"amountMinor": 150000,
"currency": "TRY",
"bookedAt": "2026-01-01T07:00:00.000Z",
"valueAt": null,
"receivedAt": "2026-01-01T07:05:00.000Z",
"canonicalType": "TRANSFER_IN",
"description": "ORNEK ACIKLAMA",
"counterpartyNameMasked": "Ö**** A****",
"counterpartyIbanMasked": "TR00 **** **** **** **** **00 00",
"receiptNo": null
}
}Envelope
| Field | Meaning |
|---|---|
id | Event ID; the same value as the x-lunixi-event-id header. |
type | bank_account.transaction.received |
organizationId | Your organization. |
productCode | BANK_CONNECT |
environment | TEST or LIVE. |
eventVersion | Event version; 1 today. |
schemaVersion | Version of the data schema; v1 today. |
createdAt | When the event was created. |
data | The transaction fields; see below. |
The envelope may contain fields not in this table; base your code only on the fields documented here.
data
| Field | Type | Description |
|---|---|---|
transactionId | string (UUID) | The transaction's ID; the same as transactionId on the Transactions endpoint. |
accountId | string (UUID) | The account the transaction belongs to. |
connectionId | string (UUID) | The account's connection. |
bankEftCode | string | The bank's EFT code. |
bankName | string | The bank's name. |
direction | string | CREDIT (in) or DEBIT (out). |
amountMinor | integer | Amount in minor units; always zero or positive. |
currency | string | Currency (ISO 4217). |
bookedAt | string | Booking date. |
valueAt | string | null | Value date. |
receivedAt | string | The moment the transaction was recorded at Lunixi. |
canonicalType | string | Bank-independent transaction type. → Transaction types |
description | string | null | The description as the bank wrote it, unmasked. |
counterpartyNameMasked | string | null | The counterparty's masked name. |
counterpartyIbanMasked | string | null | The counterparty's masked IBAN. |
receiptNo | string | null | Receipt number. |
Unlike API responses, fields without a value arrive as null in the webhook; the field is still present in the body. Code that handles both the API and the webhook must cover both cases.
Subscription
To receive the event, create a webhook endpoint for the Bank Connections product:
| Field | Value |
|---|---|
productCode | BANK_CONNECT |
eventTypes | ["bank_account.transaction.received"], or ["bank_account.*"] for all events in this family. Add ["bank_transfer.*"] for transfer events. |
environment | A TEST endpoint receives transactions in the test environment, a LIVE endpoint those in the live environment. |
Creating the endpoint, the signing secret and delivery history: Webhook Setup.
Signature, retries and deduplication
Verify the signature on every delivery; the signature format and retry policy are the same for all products: Webhooks.
Delivery is at least once; the same event may arrive more than once. Process the event idempotently by x-lunixi-event-id. The event ID is always the same for the same transaction; you can also deduplicate by data.transactionId.
Handling it in PHP
The PHP SDK verifies the signature and gives you the data fields typed:
use Lunixi\Sdk\BankConnect\BankAccountTransactionReceived;
use Lunixi\Sdk\BankConnect\BankConnectEvents;
use Lunixi\Sdk\Exception\WebhookVerificationException;
$rawBody = file_get_contents('php://input') ?: '';
try {
$event = $lunixi->webhooks()->verify($rawBody, getallheaders() ?: [], getenv('LUNIXI_WEBHOOK_SECRET'));
} catch (WebhookVerificationException $e) {
http_response_code(400);
return;
}
if ($event->type() === BankConnectEvents::TRANSACTION_RECEIVED) {
$transaction = BankAccountTransactionReceived::fromEvent($event);
// Deduplicate by $event->id() and store; e.g. $transaction->transactionId(),
// $transaction->direction(), $transaction->amountMinor(), $transaction->description()
}
http_response_code(200);The BankConnectEvents::ALL_EVENTS value (bank_account.*) can be used in the endpoint subscription.
Using the webhook together with the list endpoint
The webhook is the fast way to learn about a new transaction; it is not the only source. If your endpoint is unreachable for a while, or a transaction arrived through a path that emits no event (for example the initial read-back), fill the gap from the Transactions endpoint:
transactionId, read the list endpoint from the first page at intervals and add what you have not stored, and deduplicate the two sources by transactionId.Transfer events
When a money transfer is created and settled, Lunixi sends the events below to your endpoint. No webhook is sent for currency exchange orders; read their outcome from the Currency Exchange endpoint.
| Event | When | data.status |
|---|---|---|
bank_transfer.created | The transfer is created. A transfer created through the API is SUBMITTED; one started in the panel and waiting for the verification code is PENDING_OTP. | SUBMITTED or PENDING_OTP |
bank_transfer.completed | The bank completed the transfer. | COMPLETED |
bank_transfer.failed | The transfer did not take place; the reason is in data.failureCode. | FAILED |
bank_transfer.returned | The receiving bank returned a completed transfer. | RETURNED |
- Each event is produced at most once per transfer; redeliveries carry the same event id.
- A transfer started in the panel that is not confirmed in time, or is canceled, becomes
CANCELED; no separate event is sent for it. Read its status from the transfer endpoint. - For a transfer whose outcome is not confirmed yet (
outcomeUnknown), the event is sent once the outcome is settled. - Events can arrive out of order (for example
completedbeforecreated). Write your code againstdata.statusand the transfer's current status, not the order of events.
Sample delivery
{
"id": "00000000-0000-0000-0000-00000000e101",
"type": "bank_transfer.completed",
"organizationId": "00000000-0000-0000-0000-00000000000a",
"productCode": "BANK_CONNECT",
"environment": "LIVE",
"eventVersion": 1,
"schemaVersion": "v1",
"createdAt": "2026-01-01T07:00:21.000Z",
"data": {
"transferId": "00000000-0000-0000-0000-000000002000",
"connectionId": "00000000-0000-0000-0000-000000000010",
"accountId": "00000000-0000-0000-0000-000000000100",
"bankEftCode": "0000",
"bankName": "ÖRNEK BANKA",
"status": "COMPLETED",
"rail": "FAST",
"amountMinor": 150075,
"currency": "TRY",
"receiverIbanMasked": "TR00 **** **** **** **** **00 00",
"receiverNameMasked": "Ö**** T****** A.Ş.",
"paymentPurpose": null,
"description": "Invoice 2026-0042",
"failureCode": null,
"idempotencyKey": "payout-2026-0042",
"createdAt": "2026-01-01T07:00:00.000Z",
"completedAt": "2026-01-01T07:00:20.000Z",
"failedAt": null,
"returnedAt": null
}
}The envelope fields are the same as for the transaction event; type carries the event name.
data of a transfer event
| Field | Type | Description |
|---|---|---|
transferId | string (UUID) | The transfer's id; the same as id on the transfer endpoint. |
connectionId | string (UUID) | The account's connection. |
accountId | string (UUID) | The account the money left from. |
bankEftCode | string | The bank's EFT code. |
bankName | string | The bank's name. |
status | string | The transfer's status at the time of the event. |
rail | string | null | The route the bank chose (INTERNAL, INTRABANK, FAST, EFT, UNKNOWN). |
amountMinor | integer | Amount in minor units. |
currency | string | Currency (ISO 4217). |
receiverIbanMasked | string | The receiver's masked IBAN. |
receiverNameMasked | string | The receiver's masked name. |
paymentPurpose | string | null | Payment purpose code. |
description | string | null | Transfer description. |
failureCode | string | null | Reason code on failed and returned events. → Transfer result codes |
idempotencyKey | string | The transfer's Idempotency-Key; use it to match your own payment record. |
createdAt | string | When the transfer was created. |
completedAt | string | null | When it completed. |
failedAt | string | null | When it failed. |
returnedAt | string | null | When it was returned. |
Fields without a value arrive as null.
Handling it in PHP
use Lunixi\Sdk\BankConnect\BankTransferEvent;
use Lunixi\Sdk\BankConnect\BankTransferEvents;
$event = $lunixi->webhooks()->verify($rawBody, getallheaders() ?: [], getenv('LUNIXI_WEBHOOK_SECRET'));
if (in_array($event->type(), BankTransferEvents::ALL, true)) {
$transfer = BankTransferEvent::fromEvent($event);
// Deduplicate with $event->id(); find your payment record with $transfer->idempotencyKey().
if ($event->type() === BankTransferEvents::FAILED) {
// $transfer->failureCode()
}
}BankTransferEvents::ALL_EVENTS (bank_transfer.*) can be used in the endpoint subscription. Requires PHP SDK 0.22.0 or later.