> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.fynapse.app/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.fynapse.app/_mcp/server.

# Event Types

> Learn about different types of events you can subscribe to.

## Overview

This article provides a list of event types that can be subscribed to using webhooks. An event represents something that has happened in the system, e.g. file ingestion, etc.

> **Info**
>
> For detailed description of how to configure webhooks for these event types, refer to [Setup Webhook Subscription via API](/developer-guides/webhook-integration/setup-webhook-subscription-via-api)

---

## Event Types

### Approval Workflow Events

| Event Type                    | When Sent                                                                             |
| ----------------------------- | ------------------------------------------------------------------------------------- |
| `workflow.approval.pending`   | Workflow enters `PENDING` state; an approver at the current level is expected to act. |
| `workflow.approval.approved`  | Action submitted for approval succeeds.                                               |
| `workflow.approval.rejected`  | An approver rejects at any level.                                                     |
| `workflow.approval.cancelled` | Requestor cancels.                                                                    |
| `workflow.approval.invalid`   | Unable to complete the approved action.                                               |

**Data Fields**

| Data Type   | Description                                                                                                                                                                                                                       |
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Identifiers | `requestId`, `productArea`, `status` `productAreaCode`.                                                                                                                                                                           |
| Progress    | `level` and `totalLevels` (both `0` for auto-approve).                                                                                                                                                                            |
| Timing      | `transitionTime`: when the workflow moved into this state.                                                                                                                                                                        |
| Users       | `requestor`, `approver` (null on `pending`), `approverGroup` (Fynapse user-group name), `finalizedBy` (username of the user who took the terminal decision, or `system` for auto-approve), `requestorComment`, `approverComment`. |
| Deep-link   | `link`: an HTTPS URL to the approval list in pa-web filtered by `requestId`.                                                                                                                                                      |
| Errors      | on `invalid` only: `errorSource` (`INVALID_CONFIGURATION` when the config was invalid at request time, `APPLICATION_OF_CONFIGURATION` when downstream data application failed) and a list of `errorMessages`, `errorSummary`.     |
| Time        | `creationTime`.                                                                                                                                                                                                                   |

> **Tip**
>
> Ordering at the subscriber's endpoint is **best-effort, not guaranteed**.
> Events for a given request are published in the correct order, but if one delivery gets stuck in retries while the next succeeds, the subscriber can receive them out of order. Subscribers should rely on the `status` field (and `level` for multi-level flows) rather than on receipt order.

> **Info**
>
> * Auto-approve workflows never emit `pending`. They emit `approved` when downstream data application succeeds, or `invalid` if it fails.
> * The `approved` status requires downstream success. Internal approval by the last approver is not sufficient; `approved` is only on `DATA_APPLIED`. If the downstream service returns `DATA_FAILED`, the workflow reverts straight from internally-approved to `invalid`, and subscribers see `pending…pending → invalid` with no `approved` in between.
> * The `invalid` status has two oaths:
>   * Config-invalid path — sent at request time with no preceding event. `errorSource = INVALID_CONFIGURATION`.
>   * Data-application-failed path — preceded by one or more `pending` events (but never by `approved`). `errorSource = APPLICATION_OF_CONFIGURATION`.
> * Multi-level approvals emit one `pending` per level before the terminal event.
> * The `pending` status events always have `approver` and `approverComment` = null.

### Ingestion Events - `ingestion.processing.succeeded` and `ingestion.processing.failed`

When sent:

* `ingestion.processing.succeeded` - an ingestion finished processing without errors.
* `ingestion.processing.failed` - an ingestion finished processing in error.

The purpose of this event is to notify the receiving system that processing of a previously
submitted ingestion produced a `DONE` or `ERROR` outcome.

The outcome
may still be interim — use `isFinal` (see the field below) to tell whether it is
definitive.
The `isFinal` can be set to false, e.g.

* for `ingestion.processing.succeeded` - if the ingestion file contained Unposted Journals that are awaiting their posting date. Then once the Journals are posted when their posting date arrives, `isFinal` will change to true.
* for `ingestion.processing.failed` - if the ingestion file contained errors that are undergoing reprocessing. Then if the reprocessing is unsuccessful, `isFinal` will change to true.

Use these events to track processing without polling.

**Data Fields**

| Field              | Type                      | Description                                                                                                                                                                                             |
| ------------------ | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ingestionId`      | string (UUID)             | Identifier of the ingestion, as returned when the data was submitted.                                                                                                                                   |
| `processingStatus` | string                    | `DONE` for success, `ERROR` for failure.                                                                                                                                                                |
| `finishedOn`       | string (ISO-8601) \| null | When processing finished.                                                                                                                                                                               |
| `isFinal`          | boolean                   | `true` if this is the definitive outcome. `false` is an interim result that may be followed by another notification for the same `ingestionId` (for example while downstream flows are still settling). |

**Example**

```json
{
  "event_id": "a3efc743-ac6c-463b-aa19-4dd9c45b5b96",
  "event_type": "ingestion.processing.succeeded",
  "metadata": {},
  "event_timestamp": "2026-03-18T12:00:01.234Z",
  "data": {
    "ingestionId": "5b8f0c0e-2b7a-4f1e-9c3d-0d6a1b2c3d4e",
    "processingStatus": "DONE",
    "finishedOn": "2026-03-18T12:00:00Z",
    "isFinal": true
  }
}
```

The `failed` variant has the same shape with `event_type`
`ingestion.processing.failed` and `processingStatus` `ERROR`.

---

### Extract Upload - `extract.upload.completed` Event

When sent: An extract file was uploaded to cloud storage and is ready to download.

The purpose of this event is to notify the receiving system that an extract configured to deliver to
**cloud storage** has finished and the file has been written to the storage
bucket. The file is available to download and process.

This event is emitted only for extracts with cloud storage as destination and
only once the run has fully succeeded:

* the extraction completed
  (`extractionStatus` `COMPLETED`)
* the file was uploaded (`uploadStatus` `SENT`)
* and — if the extract is configured to produce a manifest — the manifest has
  also been delivered.

When you receive this event the file (and its manifest, if
any) is ready to download. Because of these conditions, the
`extractionStatus`, `uploadStatus` and `uploadSpace` fields are constant for this
event; they are listed in the table below for completeness.

**Data Fields**

| Field                    | Type                      | Description                                                           |
| ------------------------ | ------------------------- | --------------------------------------------------------------------- |
| `extractLogId`           | string (UUID)             | Identifier of the extract run.                                        |
| `rerunNumber`            | int                       | Rerun counter for this run (`0` for the first run).                   |
| `extractName`            | string                    | Name of the extract.                                                  |
| `configurationName`      | string                    | Name of the extract configuration.                                    |
| `extractConfigurationId` | string (UUID)             | Identifier of the extract configuration.                              |
| `startedOn`              | string (ISO-8601) \| null | When the run started.                                                 |
| `uploadedOn`             | string (ISO-8601) \| null | When the file was uploaded.                                           |
| `extractionStatus`       | string                    | Extraction outcome. Always `COMPLETED` for this event.                |
| `uploadStatus`           | string                    | Upload outcome. Always `SENT` for this event.                         |
| `uploadSpace`            | string                    | Destination of the file. Always `CLOUD_STORAGE` for this event.       |
| `executed`               | string                    | How the run was triggered: `AUTOMATICALLY` (scheduled) or `MANUALLY`. |
| `generatedBy`            | string \| null            | User or process that triggered the run.                               |

**Example**

```json
{
  "event_id": "c1d2e3f4-5678-49ab-bcde-0123456789ab",
  "event_type": "extract.upload.completed",
  "metadata": {},
  "event_timestamp": "2026-04-14T09:54:45.040Z",
  "data": {
    "extractLogId": "7f3a9b2e-1d4c-4f8a-6b0e-3d7c9a1f4b2e",
    "rerunNumber": 0,
    "extractName": "Daily balances",
    "configurationName": "daily-balances",
    "extractConfigurationId": "2b7a4f1e-9c3d-0d6a-1b2c-3d4e5f60718e",
    "startedOn": "2026-04-14T09:54:43.674Z",
    "uploadedOn": "2026-04-14T09:54:44.900Z",
    "extractionStatus": "COMPLETED",
    "uploadStatus": "SENT",
    "uploadSpace": "CLOUD_STORAGE",
    "executed": "AUTOMATICALLY",
    "generatedBy": null
  }
}
```

---

### Balance Extract - `balance.extracted` Event

> **Info**
>
> Please note that in addition to steps 1-3 from [Webhook Configuration](/developer-guides/webhook-integration/setup-webhook-subscription-via-api) Balance Extract Event requires a 4th step, adding an extract in the UI, described below.

Step 4 — Create a Balance Extract with Webhook Target

> **Info**
>
> You will need **Extractions Editor** role assigned, e.g. Editor, to create the extract on the **Extracts** screen.

* Go to Operations → Extracts → Extracts in Fynapse
* Create a new extract
* Set Target name to Webhook
* Select Balances as the data source
* Complete the configuration
* Wait for schedule to Activate or run the extract manually

See [Data Extraction](/documentation/processing-configuration/data-extraction/overview) for more details.

When the extract runs, `balance.extracted` events are generated and delivered to your webhook endpoint.

### Event Format

The purpose of this event is to deliver a `balance.extracted` event to the receiving system.

> **Info**
>
> Please note that the contents of the `data` property will be based off your selected Balance Query.

```json
{
  "event_id": "a3efc743-ac6c-463b-aa19-4dd9c45b5b96",
  "event_type": "balance.extracted",
  "metadata": {
    "eventType": "balance.extracted",
    "startedOn": "2026-04-14T09:54:43.674940Z",
    "extractConfigurationName": "test"
  },
  "event_timestamp": "2026-04-14T09:54:45.040Z",
  "data": {
    "node": "Node1",
    "account": "222",
    "balance": 1.0,
    "productName": "",
    "transactionCurrency": "GBP"
  }
}
```