_Docs/
Get StartedModulesPlatformDeployCookbookChangelogReference
_Stack
_Modules
  • Ledger
  • Numscript
  • Payments
  • WalletsEE
  • FlowsEE
  • ReconciliationEE
    • Concepts
    • Getting Started
    • Control Templates
    • Alerts and Evidence
    • Reconcile an Unsupported Provider
  • WebhooksEE
  1. Modules
  2. Reconciliation
  3. Alerts and Evidence
Reconciliation

Alerts and Evidence

Investigate discrepancies and close them with an auditable decision.

An alert is the operational case created when one asset fails a reconciliation rule. It brings together the latest evidence, current status, alert severity, reconciliation period, and a complete transition history.

Read evidence from a passing check#

A passing check does not open an alert. Its evidence is stored on the evaluation returned by POST /rules/{ruleID}/evaluate and available later from the evaluation endpoints.

Each checked asset appears in data.evidence with passed: true and a compact, self-contained proof. It contains the observed balances and the predicate inputs needed to verify the historical result, even if the rule is edited later. The proof shape depends on the template:

ledger_vs_pool_drift stores the raw Ledger and cash-pool balances with the sign and tolerance used by the check. In this example, the residual is 100 × 1 + (-95) = 5, which passes a tolerance of 5.

json
{
  "fingerprint": "asset:USD/2",
  "passed": true,
  "proof": {
    "ledger": "100",
    "ledgerSign": "1",
    "pool": "-95",
    "tolerance": "5"
  }
}

Proof amounts are integer strings in the asset's smallest unit; see Unambiguous Monetary Notation. Comparisons remain exact for arbitrarily large balances. PASS proofs omit generated expressions and derived fields, while retaining the inputs needed to verify the predicate; use pitPerSource for the source timestamps.

Investigate an alert#

Start with the alert's current state:

json
{
  "id": "<ALERT_ID>",
  "ruleID": "<RULE_ID>",
  "fingerprint": "asset:USD/2",
  "periodID": "2026-07",
  "status": "OPEN",
  "severity": "high",
  "firstSeenAt": "2026-07-20T00:00:02Z",
  "lastSeenAt": "2026-07-21T00:00:02Z",
  "occurrenceCount": 2,
  "lastEvaluationID": "<EVALUATION_ID>",
  "evidence": {
    "asset": "USD/2",
    "leftBalance": "250000",
    "rightBalance": "249500",
    "difference": "500",
    "tolerance": 0
  },
  "labels": { "team": "treasury" }
}

Review four things before acting:

  1. Rule and period: confirm which control and financial period the case belongs to.
  2. Evidence: compare the observed values, difference, and tolerance.
  3. Source timestamps: inspect the linked evaluation's pitPerSource to verify both sides were read at the intended instants.
  4. Timeline: review earlier failures and manual decisions on the same case.

Alert lists are ordered by lastSeenAt descending, then by id descending when timestamps match. This stable order prevents alerts from moving across page boundaries while following a cursor.

Retrieve the append-only timeline with:

curl -X GET $FORMANCE_API_URL/api/reconciliation/alerts/<ALERT_ID>/events
GET/api/reconciliation/alerts/<ALERT_ID>/events

The timeline is cursor-paginated and ordered most recent first. It includes fail, pass, ack, resolve, accept, snooze, and unsnooze events. A failure whose previous status was RESOLVED is marked as a reopen. If the alert does not exist, the endpoint returns 404 rather than an empty timeline.

Build a chronological view#

Fetch every page, following cursor.next while cursor.hasMore is true. Concatenate the returned cursor.data arrays, then reverse the complete list because the API returns newest events first:

typescript
async function loadAlertTimeline(
  stackUrl: string,
  alertId: string,
  token: string,
) {
  const events = []
  let next: string | undefined

  do {
    const url = new URL(
      `/api/reconciliation/alerts/${alertId}/events`,
      stackUrl,
    )
    url.searchParams.set("pageSize", "100")
    if (next) url.searchParams.set("cursor", next)

    const response = await fetch(url, {
      headers: { Authorization: `Bearer ${token}` },
    })
    if (!response.ok) throw new Error(`Timeline request failed: ${response.status}`)

    const body = await response.json()
    events.push(...body.cursor.data)
    next = body.cursor.hasMore ? body.cursor.next : undefined
  } while (next)

  return events.reverse()
}

Each entry carries prevStatus and newStatus, so the timeline can show the state transition without inferring it from neighboring rows. Use isReopen: true to highlight a failure that followed a resolution.

Event typeWhat to show
failThe discrepancy evidence in payload; follow evaluationID for that evaluation's source timestamps.
passAutomatic resolution; follow evaluationID for the passing proof and pitPerSource.
ackThe operator, timestamp, and note in payload.
resolveThe fixed_by_booking resolution, including its author, note, and transaction references.
acceptThe accepted_by_business resolution and its frozen evidence snapshot.
snooze / unsnoozeWhen notification muting began, changed, or ended.

Do not reconstruct history from the current alert row. Its ack, resolution, and snooze fields describe only the current state. The events endpoint is the source of truth for prior transitions and resolutions, including those cleared when an alert reopened.

Acknowledge ownership#

Acknowledge an alert when someone has started investigating it:

curl -X POST $FORMANCE_API_URL/api/reconciliation/alerts/<ALERT_ID>/ack \
  -H "Content-Type: application/json" \
  -d '{
    "by": "analyst@example.com",
    "note": "Checking provider settlement files"
  }'
POST/api/reconciliation/alerts/<ALERT_ID>/ack

Acknowledgement changes OPEN to ACKNOWLEDGED. It does not make the period green and does not stop later evaluations. If another failure arrives, the alert returns to OPEN so the prior acknowledgement cannot hide new evidence.

Snooze notifications#

Use a snooze for a known, time-bounded situation such as a provider maintenance window or an in-flight migration:

curl -X POST $FORMANCE_API_URL/api/reconciliation/alerts/<ALERT_ID>/snooze \
  -H "Content-Type: application/json" \
  -d '{
    "by": "analyst@example.com",
    "until": "2026-07-21T18:00:00Z",
    "note": "Provider settlement replay in progress"
  }'
POST/api/reconciliation/alerts/<ALERT_ID>/snooze

A snooze mutes webhook notifications until until; it does not change the alert status. Evaluations continue, failures remain in the timeline, and the alert continues to count against the period's green status.

The first failure at or after the snooze expires clears it and notifies once. You can also lift it early:

curl -X POST $FORMANCE_API_URL/api/reconciliation/alerts/<ALERT_ID>/unsnooze \
  -H "Content-Type: application/json" \
  -d '{
    "by": "analyst@example.com"
  }'
POST/api/reconciliation/alerts/<ALERT_ID>/unsnooze

Reconciliation automatically suppresses webhook delivery for repeated failures whose evidence is materially unchanged. The event is still recorded and occurrenceCount still increases. Changed evidence, reopens, and manual transitions continue to notify.

Record a corrective booking#

After correcting the financial state, close the alert and optionally link the Ledger transactions that made the correction:

curl -X POST $FORMANCE_API_URL/api/reconciliation/alerts/<ALERT_ID>/resolve \
  -H "Content-Type: application/json" \
  -d '{
    "by": "analyst@example.com",
    "note": "Booked the missing provider fee",
    "transactionRefs": [
      "<LEDGER_TRANSACTION_ID>"
    ]
  }'
POST/api/reconciliation/alerts/<ALERT_ID>/resolve

This records a fixed_by_booking resolution. A later failing evaluation within the same period reopens the same alert; the earlier resolution remains in the event timeline.

An alert also closes automatically when a later evaluation passes. Automatic closure is recorded as an auto resolution.

Accept a known discrepancy#

Sometimes the observed difference is valid but should remain visible—for example, a confirmed settlement timing difference at period close. Accept it with an explicit business decision:

curl -X POST $FORMANCE_API_URL/api/reconciliation/alerts/<ALERT_ID>/accept \
  -H "Content-Type: application/json" \
  -d '{
    "by": "controller@example.com",
    "note": "Confirmed settlement lag; cash arrived in the next banking window"
  }'
POST/api/reconciliation/alerts/<ALERT_ID>/accept

Acceptance requires an author and non-empty note. Reconciliation freezes the alert's current evidence into the accepted_by_business resolution so later evidence cannot rewrite what was approved.

Acceptance closes the operational case; it does not change Ledger or external balances. Apply your organization's approval policy before treating an accepted alert as reconciled.

Use webhook events#

Reconciliation publishes alert transitions through the Formance event bus for delivery by the Webhooks module. Subscribe only to the transitions your workflow needs:

EventMeaning
reconciliation.opened_alertA new asset and period failed.
reconciliation.updated_alertAn active discrepancy changed materially.
reconciliation.acknowledged_alertAn operator took ownership.
reconciliation.resolved_alertThe control passed or an operator recorded a fix.
reconciliation.accepted_alertAn authorized user accepted the discrepancy.
reconciliation.reopened_alertA resolved case failed again in the same period.
reconciliation.snoozed_alertNotifications were muted until a future time.
reconciliation.unsnoozed_alertA mute was lifted early.

Each payload contains the current alert and the event that caused the transition. Use the alert-event ID as the downstream idempotency key: webhook delivery can be retried and should not be treated as exactly once.

Separate financial and operational failures#

If a source read or evaluation cannot complete, Reconciliation stores an ERROR evaluation and opens an alert with fingerprint engine.error and label kind=engine.error. Route this signal to the platform team rather than treating it as a confirmed financial discrepancy.

Resolve the source or execution problem, then run the rule again. A successful evaluation closes the operational error path and produces the normal financial verdict.

Control TemplatesReconcile an Unsupported Provider
On This Page
  • Read evidence from a passing check
  • Investigate an alert
  • Build a chronological view
  • Acknowledge ownership
  • Snooze notifications
  • Record a corrective booking
  • Accept a known discrepancy
  • Use webhook events
  • Separate financial and operational failures