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

# Webhooks and callbacks

> Emit Matcher lifecycle events to external tools through outbound webhooks, and receive resolution callbacks so JIRA or ServiceNow keep exceptions in sync.

Webhooks enable real-time communication between Matcher and external systems. This guide covers outbound event notifications and inbound resolution callbacks.

## Overview

***

Matcher supports bidirectional webhook communication, keeping your operational tools in sync with every reconciliation event in real time. This reduces manual intervention, helps maintain SLA compliance, and ensures a continuous audit trail across all connected systems.

* **Outbound webhooks**: exception routing dispatches exceptions to external targets — JIRA, ServiceNow, or an HTTP webhook endpoint you configure
* **Inbound callbacks**: External systems notify Matcher when actions are taken

When an exception is routed to a webhook target, Matcher delivers a signed HTTP request to your endpoint. External systems like JIRA or ServiceNow can then send callbacks to update exception status or close items automatically. This two-way flow keeps your tools in sync without manual intervention.

Beyond exception dispatch, Matcher publishes its full lifecycle event catalog on the platform's streaming backbone — those events are consumed as a stream, not delivered as HTTP webhooks.

<Frame caption="Two-way flow between Matcher and external systems.">
  <img src="https://mintcdn.com/lerian-49cb71fc/SEOef3JqTInYAAau/images/en/d2/matcher-webhooks-callbacks.svg?fit=max&auto=format&n=SEOef3JqTInYAAau&q=85&s=4555e18062585a563a2285ae419a9dc0" alt="Matcher Webhooks Callbacks" width="724" height="520" data-path="images/en/d2/matcher-webhooks-callbacks.svg" />
</Frame>

## Outbound events

***

Matcher emits events when significant actions occur in the reconciliation process. The catalog below is published on the streaming backbone; exception events additionally reach HTTP webhook endpoints through [exception routing](/en/matcher/configuration/matcher-exception-routing).

### Available events

Matcher's event catalog is defined centrally. The most commonly consumed events are grouped by domain below.

**Configuration**

| Event                            | Trigger                            | Typical Use            |
| -------------------------------- | ---------------------------------- | ---------------------- |
| `reconciliation_context.created` | Reconciliation context created     | Provisioning sync      |
| `reconciliation_context.updated` | Context metadata or status changed | Config change tracking |
| `reconciliation_context.deleted` | Context deleted                    | Downstream teardown    |
| `reconciliation_source.created`  | Source created within a context    | Source onboarding      |
| `match_rule.created`             | Match rule created                 | Rule change audit      |
| `match_rule.reordered`           | Rule priorities reordered          | Rule change audit      |

**Discovery (Fetcher)**

| Event                              | Trigger                                     | Typical Use           |
| ---------------------------------- | ------------------------------------------- | --------------------- |
| `fetcher_connection.synced`        | Connection and schema snapshot synchronized | Discovery monitoring  |
| `fetcher_connection.unreachable`   | Connection marked unreachable               | Connectivity alerting |
| `extraction_request.created`       | Extraction request created                  | Extraction monitoring |
| `extraction_request.submitted`     | Extraction accepted by Fetcher              | Extraction monitoring |
| `extraction_request.completed`     | Extraction completed with artifact          | Data readiness        |
| `extraction_request.failed`        | Extraction failed                           | Error alerting        |
| `extraction_request.cancelled`     | Extraction cancelled                        | Pipeline monitoring   |
| `extraction_request.bridged`       | Extraction linked to an ingestion job       | Pipeline monitoring   |
| `extraction_request.bridge_failed` | Bridge to ingestion failed                  | Error alerting        |

**Ingestion**

| Event                 | Trigger                              | Typical Use              |
| --------------------- | ------------------------------------ | ------------------------ |
| `ingestion.completed` | File import finished                 | Data pipeline monitoring |
| `ingestion.failed`    | File import failed                   | Error alerting           |
| `transaction.ignored` | Unmatched transaction marked ignored | Audit trail              |

**Matching**

| Event                        | Trigger                              | Typical Use               |
| ---------------------------- | ------------------------------------ | ------------------------- |
| `match_run.completed`        | Match job finished                   | Job monitoring, reporting |
| `match_run.failed`           | Match job failed                     | Error alerting            |
| `match_group.confirmed`      | Match group confirmed                | Downstream updates        |
| `match_group.unmatched`      | Confirmed match reverted             | Correction tracking       |
| `transaction.matched`        | Transaction marked matched           | Audit logging             |
| `transaction.pending_review` | Non-automatic candidate needs review | Review queue triggers     |
| `fee_variance.created`       | Fee variance detected                | Fee investigation         |

**Exceptions and disputes**

| Event                             | Trigger                                 | Typical Use        |
| --------------------------------- | --------------------------------------- | ------------------ |
| `exception.assigned`              | Exception assigned to an owner          | User notification  |
| `exception.resolved`              | Exception resolved                      | Status sync        |
| `exception.dispatched`            | Exception sent to an external target    | Ticket creation    |
| `exception.callback_processed`    | External callback processed             | Status sync        |
| `exception.force_match_resolved`  | Exception resolved via force match      | Approval workflows |
| `exception.adjust_entry_resolved` | Exception resolved via adjustment entry | Audit trail        |
| `exception_comment.added`         | Comment added to an exception           | Collaboration sync |
| `exception_comment.deleted`       | Exception comment deleted               | Collaboration sync |
| `dispute.opened`                  | Dispute opened for an exception         | Dispute tracking   |
| `dispute.won`                     | Dispute closed as won                   | Status sync        |
| `dispute.lost`                    | Dispute closed as lost                  | Status sync        |
| `evidence.submitted`              | Evidence submitted to a dispute         | Dispute tracking   |

**Governance and reporting**

| Event                      | Trigger                        | Typical Use           |
| -------------------------- | ------------------------------ | --------------------- |
| `audit_log.created`        | Audit log entry appended       | Compliance monitoring |
| `archive_metadata.created` | Archive lifecycle started      | Archival monitoring   |
| `archive.uploaded`         | Archive object uploaded        | Archival monitoring   |
| `archive.completed`        | Archive verified and completed | Archival monitoring   |
| `actor.pseudonymized`      | Actor mapping pseudonymized    | Compliance monitoring |
| `export_job.created`       | Export job queued              | Export monitoring     |
| `export_job.succeeded`     | Export job completed           | Download readiness    |
| `export_job.failed`        | Export job failed              | Error alerting        |
| `export_job.expired`       | Export artifact expired        | Export lifecycle      |

### Webhook delivery payload

Exception dispatches to a webhook target carry a consistent payload — `eventId`, `eventType`, `timestamp`, the exception snapshot under `data`, and routing/tracing information under `metadata`:

```json theme={null}
{
  "eventId": "0e8f1c2a-5b6d-4f3e-9a7b-1c2d3e4f5a6b",
  "eventType": "exception.dispatched",
  "timestamp": "2026-01-20T10:30:00Z",
  "data": {
    "exceptionId": "9b2f4e6a-1c3d-4e5f-8a9b-0c1d2e3f4a5b",
    "transactionId": "7a1b3c5d-9e8f-4a2b-b6c7-d8e9f0a1b2c3",
    "severity": "HIGH",
    "status": "PENDING",
    "amount": "15000.00",
    "currency": "USD",
    "reason": "No matching ledger entry found",
    "sourceType": "LEFT",
    "createdAt": "2026-01-20T10:29:15Z",
    "dueAt": "2026-01-23T10:29:15Z"
  },
  "metadata": {
    "traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
    "target": "WEBHOOK",
    "queue": "ops-review",
    "ruleName": "high-value-unmatched",
    "assignee": "ops-team"
  }
}
```

`data.dueAt` and the `metadata` fields `traceId`, `queue`, `ruleName`, and `assignee` are omitted when not set. Streaming catalog events (the tables above) follow their own per-event schemas on the event stream and are not delivered in this HTTP shape.

## Inbound callbacks

***

External systems send callbacks to Matcher to update exception status after processing. The callback endpoint accepts status updates, resolution notes, and assignee changes from any external system.

### Process a callback

The callback endpoint is authenticated by the `X-Callback-Token` header — an opaque token minted through the [callback credentials](#callback-credentials) surface — not by an operator JWT. All fields shown below are required; `dueAt` and `updatedAt` accept `null`, and `payload` can be an empty object:

```bash cURL theme={null}
curl -X POST "https://api.matcher.example.com/v1/exceptions/{exceptionId}/callback" \
 -H "X-Callback-Token: ***" \
 -H "X-Idempotency-Key: callback-jira-1234" \
 -H "Content-Type: application/json" \
 -d '{
   "callbackType": "status_update",
   "externalSystem": "JIRA",
   "externalIssueId": "RECON-1234",
   "status": "RESOLVED",
   "resolutionNotes": "Verified: amount difference is expected bank wire fee",
   "assignee": "john.doe@company.com",
   "dueAt": null,
   "updatedAt": "2026-01-20T14:30:00Z",
   "payload": {}
 }'
```

The `externalSystem` field identifies the external system that processed the exception. Common values include `"JIRA"`, `"SERVICENOW"`, or `"WEBHOOK"`, but callbacks can report any system identifier. Omitting any of the nine required fields returns a `422`.

#### Response

```json theme={null}
{
  "status": "accepted"
}
```

<Tip>
  API Reference: [Process callback](/en/reference/matcher/process-exception-callback)
</Tip>

When Matcher processes a callback, it updates the exception status and records the resolution in the audit trail. Use the `X-Idempotency-Key` header to prevent duplicate processing.

### Automatic retry for failed callbacks

If a previous callback for the same idempotency key failed during processing, Matcher automatically attempts to reacquire the idempotency lock and reprocess the callback. This means you don't need to generate a new idempotency key when retrying a failed callback — simply resend the same request and Matcher handles the recovery.

The retry behavior applies only to callbacks that were marked as `failed` internally. Callbacks that completed successfully are still deduplicated as expected.

## Callback credentials

***

Inbound callbacks are authenticated with an opaque bearer token that the external system sends in the `X-Callback-Token` header. These **callback credentials** are minted, listed, rotated, and revoked through a dedicated CRUD surface under `/v1/exceptions/callbacks/credentials`. Each credential is bound to the caller's tenant, and only the token's SHA-256 hash is stored server-side — the raw token is returned **exactly once** at mint/rotate time.

| Action            | Method & path                                                     | Notes                                                                                                                                                              |
| ----------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Mint credential   | `POST /v1/exceptions/callbacks/credentials`                       | Creates a credential and returns the raw token once (`201`).                                                                                                       |
| List credentials  | `GET /v1/exceptions/callbacks/credentials`                        | Lists credential metadata for the tenant (never the raw tokens).                                                                                                   |
| Rotate credential | `POST /v1/exceptions/callbacks/credentials/{credentialId}/rotate` | Atomically supersedes a live credential with a freshly minted one (same label) and returns the new raw token once; the old one is revoked in the same transaction. |
| Revoke credential | `DELETE /v1/exceptions/callbacks/credentials/{credentialId}`      | Terminally revokes a credential (`204`); append-only audited.                                                                                                      |

<Tip>
  API Reference:

  * [Mint callback credential](/en/reference/matcher/mint-callback-credential)
  * [List callback credentials](/en/reference/matcher/list-callback-credentials)
  * [Rotate callback credential](/en/reference/matcher/rotate-callback-credential)
  * [Revoke callback credential](/en/reference/matcher/revoke-callback-credential)
</Tip>

### Mint a credential

The request body is optional; provide `externalSystem` as an operator-legible label for the system this token authenticates.

```bash cURL theme={null}
curl -X POST "https://api.matcher.example.com/v1/exceptions/callbacks/credentials" \
 -H "Authorization: Bearer $TOKEN" \
 -H "Content-Type: application/json" \
 -d '{ "externalSystem": "stripe" }'
```

The `201` response (`CredentialSecretResponse`) returns:

| Field            | Description                                                                                                               |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `token`          | Raw bearer token, surfaced **once**. Configure it as the `X-Callback-Token` header value in the external system.          |
| `credentialId`   | Surrogate id of the minted credential (used for rotate/revoke).                                                           |
| `createdAt`      | Mint time (RFC 3339, UTC).                                                                                                |
| `externalSystem` | The label echoed back for confirmation.                                                                                   |
| `webhookUrlHint` | Informational inbound-callback URL shape to configure externally; the `{exceptionId}` placeholder is filled per callback. |

<Warning>
  The raw `token` is shown only in the mint and rotate responses. Store it securely on receipt — it cannot be retrieved again. If it is lost or leaked, rotate or revoke the credential.
</Warning>

### Rotate a credential

```bash cURL theme={null}
curl -X POST "https://api.matcher.example.com/v1/exceptions/callbacks/credentials/{credentialId}/rotate" \
 -H "Authorization: Bearer $TOKEN"
```

Rotation returns a new `CredentialSecretResponse` (new raw token) and revokes the previous credential atomically, so external callers experience no gap when you swap the token.

### Revoke a credential

```bash cURL theme={null}
curl -X DELETE "https://api.matcher.example.com/v1/exceptions/callbacks/credentials/{credentialId}" \
 -H "Authorization: Bearer $TOKEN"
```

Revocation is terminal: the credential can no longer authenticate inbound callbacks.

## Webhook security

***

### Signature verification

When a webhook shared secret is configured, Matcher signs each delivery with an HMAC-SHA256 over the **raw request body** and sends it in the `X-Signature-256` header, formatted as `sha256=<hex-digest>`:

```
X-Signature-256: sha256=abc123...
```

Each delivery also carries an `X-Idempotency-Key` header so receivers can deduplicate retries.

**Verification process:**

1. Compute HMAC-SHA256 of the raw request body using the webhook shared secret
2. Prefix the hex digest with `sha256=`
3. Compare (constant-time) with the `X-Signature-256` header

**Example (Node.js):**

```javascript theme={null}
const crypto = require('crypto');

function verifyWebhook(payload, signature, secret) {
 const expectedSignature = crypto
 .createHmac('sha256', secret)
 .update(payload)
 .digest('hex');

 return `sha256=${expectedSignature}` === signature;
}
```

**Example (Python):**

```python theme={null}
import hmac
import hashlib

def verify_webhook(payload, signature, secret):
 expected = hmac.new(
 secret.encode(),
 payload,
 hashlib.sha256
 ).hexdigest()
 return f"sha256={expected}" == signature
```

### Network posture

Matcher deploys in your own infrastructure, so webhook deliveries originate from your deployment's egress — there is no fixed Lerian IP range to allowlist. Serve webhook endpoints over HTTPS with a valid certificate. As an SSRF guard, Matcher refuses to deliver to private or loopback IP addresses unless the deployment explicitly allows them (development only).

## Retry logic

***

Failed webhook deliveries are retried with exponential backoff.

### Default retry policy

A failed delivery is retried up to **3 times** by default. Delays follow exponential backoff from a **1-second** base, with jitter added to spread retries — so the exact spacing varies from attempt to attempt rather than following a fixed ladder.

### Retry conditions

Retries occur for:

* HTTP 429 responses
* HTTP 5xx responses
* Transport errors (connection failures, timeouts)

No retry for:

* Other HTTP 4xx responses

## Best practices

***

<AccordionGroup>
  <Accordion title="Verify webhook signatures">
    Always verify the HMAC signature before processing webhook payloads. This prevents spoofed requests.
  </Accordion>

  <Accordion title="Respond quickly">
    Return a 2xx response within 5 seconds. Process the event asynchronously if needed.
  </Accordion>

  <Accordion title="Handle duplicates idempotently">
    Deliveries may arrive more than once. Deduplicate on the `X-Idempotency-Key` header or the payload's `eventId`.
  </Accordion>

  <Accordion title="Monitor delivery health">
    Set up alerts for webhook failure rates. Investigate persistent failures promptly.
  </Accordion>
</AccordionGroup>

## Next steps

***

<Card title="Exception Routing" icon="route" href="/en/matcher/configuration/matcher-exception-routing" horizontal>
  Configure how exceptions trigger webhook events.
</Card>

<Card title="External Sources" icon="building-columns" href="/en/matcher/integrations/matcher-external-sources" horizontal>
  Set up data sources that can push via webhooks.
</Card>
