diff --git a/api-reference/fx-webhook/events/account/asset-activated.mdx b/api-reference/fx-webhook/events/account/asset-activated.mdx
index 6687546..ee54369 100644
--- a/api-reference/fx-webhook/events/account/asset-activated.mdx
+++ b/api-reference/fx-webhook/events/account/asset-activated.mdx
@@ -1,5 +1,5 @@
---
-title: "ACCOUNT_ASSET_ACTIVATED"
+title: "ASSET_ACTIVATED"
description: "Fires when an account asset finishes onboarding and funding instructions are available."
-openapi: "apis/fx-webhook/openapi.yml webhook ACCOUNT_ASSET_ACTIVATED"
+openapi: "apis/fx-webhook/openapi.yml webhook ASSET_ACTIVATED"
---
diff --git a/api-reference/fx-webhook/events/operation/operation-completed.mdx b/api-reference/fx-webhook/events/operation/operation-completed.mdx
new file mode 100644
index 0000000..3c3998e
--- /dev/null
+++ b/api-reference/fx-webhook/events/operation/operation-completed.mdx
@@ -0,0 +1,5 @@
+---
+title: "OPERATION_COMPLETED"
+description: "Fires when a payment operation reaches its terminal success state."
+openapi: "apis/fx-webhook/openapi.yml webhook OPERATION_COMPLETED"
+---
diff --git a/api-reference/fx-webhook/events/operation/operation-failed.mdx b/api-reference/fx-webhook/events/operation/operation-failed.mdx
new file mode 100644
index 0000000..a4cd99f
--- /dev/null
+++ b/api-reference/fx-webhook/events/operation/operation-failed.mdx
@@ -0,0 +1,5 @@
+---
+title: "OPERATION_FAILED"
+description: "Fires when a payment operation reaches its terminal failure state."
+openapi: "apis/fx-webhook/openapi.yml webhook OPERATION_FAILED"
+---
diff --git a/apis/fx-webhook/openapi.yml b/apis/fx-webhook/openapi.yml
index 97fad92..04a7df9 100644
--- a/apis/fx-webhook/openapi.yml
+++ b/apis/fx-webhook/openapi.yml
@@ -140,11 +140,13 @@ components:
type: string
description: The specific event type within a resource, sent as the `X-Event-Type` header on each delivery.
enum:
- - ACCOUNT_ASSET_ACTIVATED
+ - ASSET_ACTIVATED
- BENEFICIARY_PAYMENT_INSTRUCTION_CREATED
- BENEFICIARY_PAYMENT_INSTRUCTION_APPROVED
- BENEFICIARY_PAYMENT_INSTRUCTION_REJECTED
- OPERATION_REQUESTED
+ - OPERATION_COMPLETED
+ - OPERATION_FAILED
example: OPERATION_REQUESTED
ExecutionLogStatus:
type: string
@@ -1079,11 +1081,13 @@ components:
OperationEvent:
type: object
description: >
- Payload delivered when an operation is first created
- (`OPERATION_REQUESTED`). Subsequent state transitions
- (`PROCESSING`, `COMPLETED`, `FAILED`, etc.) are not published as
- webhook events — poll `GET /api/operations/{operationId}` to
- track lifecycle progress.
+ Payload delivered for operation lifecycle events
+ (`OPERATION_REQUESTED`, `OPERATION_COMPLETED`, `OPERATION_FAILED`).
+ Inspect the `X-Event-Type` header — or `currentState.status` on
+ the payload — to tell which transition fired. Intermediate
+ statuses (`PROCESSING`, `ON_HOLD`, `ACTION_REQUIRED`) are not
+ published as webhooks; fetch
+ `GET /api/operations/{operationId}` if you need them.
properties:
id:
type: string
@@ -1091,6 +1095,8 @@ components:
readOnly: true
customerId:
type: string
+ format: uuid
+ example: "9c7b6a2e-1d3f-4a5b-8c9d-0e1f2a3b4c5d"
account:
$ref: "#/components/schemas/AccountReferenceEvent"
sourceAmount:
@@ -1123,6 +1129,9 @@ components:
value: "Amazon"
- key: "compliance-tier"
value: "high"
+ currentState:
+ $ref: "#/components/schemas/OperationState"
+ description: Status the operation is in at the time the event fires.
atTime:
type: string
format: date-time
@@ -1136,7 +1145,57 @@ components:
- intent
- fees
- transactions
+ - currentState
- atTime
+ OperationStatus:
+ type: string
+ description: Lifecycle status of an operation.
+ enum:
+ - REQUESTED
+ - PROCESSING
+ - ON_HOLD
+ - ACTION_REQUIRED
+ - COMPLETED
+ - FAILED
+ example: COMPLETED
+ Reason:
+ type: object
+ description: Machine- and human-readable explanation. Populated on an `OperationState` when the status transition requires justification (e.g., `FAILED`).
+ properties:
+ code:
+ type: string
+ description: Machine-readable code.
+ example: "INSUFFICIENT_BALANCE"
+ message:
+ type: string
+ description: Human-readable description.
+ example: "Insufficient balance for the requested operation"
+ details:
+ type: object
+ additionalProperties: true
+ description: Free-form context relevant to the reason.
+ required:
+ - code
+ - message
+ - details
+ OperationState:
+ type: object
+ description: A single entry in an operation's state history. Carries a status, optional reason, and the time the state was entered.
+ properties:
+ status:
+ $ref: "#/components/schemas/OperationStatus"
+ reason:
+ allOf:
+ - $ref: "#/components/schemas/Reason"
+ nullable: true
+ description: Justification for entering this status. Present for `FAILED`; `null` for `REQUESTED` and `COMPLETED`.
+ createdAt:
+ type: string
+ format: date-time
+ description: Time the operation entered this status.
+ required:
+ - status
+ - createdAt
paths:
/api/subscriptions:
@@ -1466,7 +1525,7 @@ paths:
$ref: "#/components/schemas/ResourceReference"
webhooks:
- ACCOUNT_ASSET_ACTIVATED:
+ ASSET_ACTIVATED:
post:
summary: Account asset activated
description: Fires when an account asset finishes onboarding and funding instructions are available.
@@ -1544,7 +1603,49 @@ webhooks:
OPERATION_REQUESTED:
post:
summary: Operation requested
- description: Fires when a payment operation (deposit, withdrawal, swap) is created.
+ description: >
+ Fires when a payment operation (deposit, withdrawal, transfer,
+ swap) is created. Compliance review and rail processing happen
+ after this event — wait for `OPERATION_COMPLETED` or
+ `OPERATION_FAILED` to know the terminal outcome.
+ tags:
+ - Operation
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/OperationEvent"
+ responses:
+ "200":
+ $ref: "#/components/responses/WebhookAck"
+ OPERATION_COMPLETED:
+ post:
+ summary: Operation completed
+ description: >
+ Fires when a payment operation reaches its terminal success
+ state. The payload carries `currentState.status: COMPLETED`,
+ with `transactions` populated by the rail-confirmed legs that
+ settled the operation.
+ tags:
+ - Operation
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/OperationEvent"
+ responses:
+ "200":
+ $ref: "#/components/responses/WebhookAck"
+ OPERATION_FAILED:
+ post:
+ summary: Operation failed
+ description: >
+ Fires when a payment operation reaches its terminal failure
+ state. The payload carries `currentState.status: FAILED` and
+ `currentState.reason` describing why. No further events fire
+ for this operation.
tags:
- Operation
requestBody:
diff --git a/docs.json b/docs.json
index 3298212..6159423 100644
--- a/docs.json
+++ b/docs.json
@@ -219,7 +219,9 @@
"group": "Operation",
"icon": "arrows-rotate",
"pages": [
- "api-reference/fx-webhook/events/operation/operation-requested"
+ "api-reference/fx-webhook/events/operation/operation-requested",
+ "api-reference/fx-webhook/events/operation/operation-completed",
+ "api-reference/fx-webhook/events/operation/operation-failed"
]
},
{
diff --git a/journeys/deposit.mdx b/journeys/deposit.mdx
index 50f8fd5..02a3acc 100644
--- a/journeys/deposit.mdx
+++ b/journeys/deposit.mdx
@@ -61,7 +61,7 @@ Deposits credit an account when funds arrive via the chosen funding rail. The cu
- Poll `GET /api/operations/{operationId}` until the inbound payment is reconciled and the operation transitions to `COMPLETED`. Lifecycle transitions are not published as webhook events; the `OPERATION_REQUESTED` webhook only fires on creation.
+ Subscribe to `OPERATION_COMPLETED` to receive the deposit confirmation once the inbound payment is reconciled, or `OPERATION_FAILED` if reconciliation fails. Both deliver the same payload as `OPERATION_REQUESTED`, with `currentState.status` set to the new status (and `currentState.reason` populated on failure). The intermediate `PROCESSING` status is not published as a webhook; poll `GET /api/operations/{operationId}` if you need to surface it in your UI.
```bash
curl --request GET \
diff --git a/journeys/swap.mdx b/journeys/swap.mdx
index 7a5716c..210f684 100644
--- a/journeys/swap.mdx
+++ b/journeys/swap.mdx
@@ -54,7 +54,7 @@ Swaps convert funds between assets within the same account — for example, BRL
- Poll `GET /api/operations/{operationId}` to follow the operation through `PROCESSING` to `COMPLETED` (or `FAILED`). Lifecycle transitions are not published as webhook events; the `OPERATION_REQUESTED` webhook only fires on creation.
+ Subscribe to `OPERATION_COMPLETED` and `OPERATION_FAILED` to receive the terminal outcome — both deliver the same payload as `OPERATION_REQUESTED`, with `currentState.status` set to the new status (and `currentState.reason` populated on failure). The intermediate `PROCESSING` status is not published as a webhook; poll `GET /api/operations/{operationId}` if you need to surface it in your UI.
```bash
curl --request GET \
diff --git a/journeys/transfer.mdx b/journeys/transfer.mdx
index c7bca3f..0739a1a 100644
--- a/journeys/transfer.mdx
+++ b/journeys/transfer.mdx
@@ -38,7 +38,7 @@ Transfers move funds between two accounts owned by the same customer. They are s
- Poll `GET /api/operations/{operationId}` to follow the operation through `PROCESSING` to `COMPLETED` (or `FAILED`). Lifecycle transitions are not published as webhook events; the `OPERATION_REQUESTED` webhook only fires on creation.
+ Subscribe to `OPERATION_COMPLETED` and `OPERATION_FAILED` to receive the terminal outcome — both deliver the same payload as `OPERATION_REQUESTED`, with `currentState.status` set to the new status (and `currentState.reason` populated on failure). The intermediate `PROCESSING` status is not published as a webhook; poll `GET /api/operations/{operationId}` if you need to surface it in your UI.
```bash
curl --request GET \
diff --git a/journeys/withdrawal.mdx b/journeys/withdrawal.mdx
index 67cea85..5645eb5 100644
--- a/journeys/withdrawal.mdx
+++ b/journeys/withdrawal.mdx
@@ -95,7 +95,7 @@ Withdrawals move funds out of an account to an external destination — a bank a
- Poll `GET /api/operations/{operationId}` to follow the operation through `PROCESSING` to `COMPLETED` (or `FAILED`). Compliance review can pause the operation in `ON_HOLD` or `ACTION_REQUIRED` until additional checks clear. Lifecycle transitions are not published as webhook events; the `OPERATION_REQUESTED` webhook only fires on creation.
+ Subscribe to `OPERATION_COMPLETED` and `OPERATION_FAILED` to receive the terminal outcome — both deliver the same payload as `OPERATION_REQUESTED`, with `currentState.status` set to the new status (and `currentState.reason` populated on failure). Intermediate statuses (`PROCESSING`, `ON_HOLD`, `ACTION_REQUIRED`) are not published as webhooks; poll `GET /api/operations/{operationId}` if you need to surface them in your UI.
```bash
curl --request GET \
diff --git a/webhooks/overview.mdx b/webhooks/overview.mdx
index 4d7f626..c53eaa2 100644
--- a/webhooks/overview.mdx
+++ b/webhooks/overview.mdx
@@ -66,15 +66,19 @@ Browse the full event catalog by resource:
| Resource | Events | Description |
| --- | --- | --- |
| [Account](/api-reference/fx-webhook/events/account/asset-activated) | 1 | Asset onboarding completion |
-| [Operation](/api-reference/fx-webhook/events/operation/operation-requested) | 1 | Operation creation |
+| [Operation](/api-reference/fx-webhook/events/operation/operation-requested) | 3 | Operation creation, completion, and failure |
| [Beneficiary](/api-reference/fx-webhook/events/beneficiary/beneficiary-payment-instruction-created) | 3 | Payment instruction creation, approval, and rejection |
You can also fetch the catalog programmatically: [`GET /references/ResourceName/all`](/api-reference/fx-webhook/subscriptions/list-resource-references).
- Webhook events fire on **creation and review outcomes only**. Operation
- lifecycle transitions (`PROCESSING`, `COMPLETED`, `FAILED`, `ON_HOLD`,
- `ACTION_REQUIRED`) and account state changes are **not** published as
- webhooks — poll `GET /api/operations/{operationId}` or
- `GET /api/accounts/{accountId}` to track them.
+ Operations publish webhooks on creation (`OPERATION_REQUESTED`) and
+ on terminal outcomes (`OPERATION_COMPLETED`, `OPERATION_FAILED`).
+ Intermediate statuses (`PROCESSING`, `ON_HOLD`, `ACTION_REQUIRED`)
+ are not published — poll `GET /api/operations/{operationId}` if
+ you need them.
+
+ Accounts publish only `ASSET_ACTIVATED`. Other account state
+ changes are not published — poll `GET /api/accounts/{accountId}` if
+ you need them.