> ## 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.

* **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 they take an action

When exception routing sends an exception 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 reach you as a stream, not 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. Matcher publishes the catalog below on the streaming backbone. Exception events also reach HTTP webhook endpoints through [exception routing](/en/products/matcher/configuration/matcher-exception-routing).

### Available events

Matcher defines its event catalog centrally. The tables below group the most commonly consumed events by domain.

**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**

| 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 the extraction engine | 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 with `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"
  }
}
```

Matcher omits `data.dueAt` and the `metadata` fields `traceId`, `queue`, `ruleName`, and `assignee` when they have no value. Streaming catalog events (the tables above) follow their own per-event schemas on the event stream. Matcher does not deliver them 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 `X-Callback-Token` header authenticates the callback endpoint. An operator JWT does not authenticate it. The header carries an opaque token from the [callback credentials](#callback-credentials) surface. Every field below is mandatory. `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/products/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. Resend the same request and Matcher handles the recovery.

The retry behavior applies only to callbacks with an internal `failed` state. Matcher still deduplicates callbacks that completed successfully.

## Callback credentials

***

An opaque bearer token authenticates inbound callbacks. The external system sends this token in the `X-Callback-Token` header. You mint, list, rotate, and revoke these **callback credentials** through a dedicated CRUD surface under `/v1/exceptions/callbacks/credentials`. Each credential belongs to the caller's tenant. Matcher stores only the token's SHA-256 hash server-side. The mint and rotate responses return the raw token **exactly once**.

| 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/products/matcher/mint-callback-credential)
  * [List callback credentials](/en/reference/products/matcher/list-callback-credentials)
  * [Rotate callback credential](/en/reference/products/matcher/rotate-callback-credential)
  * [Revoke callback credential](/en/reference/products/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>
  Only the mint and rotate responses show the raw `token`. Store it securely on receipt. You cannot retrieve it again. If you lose or leak the token, 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 you configure a webhook shared secret, Matcher signs each delivery with an HMAC-SHA256 over the **raw request body**. Matcher sends the signature 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

***

Matcher retries failed webhook deliveries with exponential backoff.

### Default retry policy

By default, Matcher retries a failed delivery up to **3 times**. Delays follow exponential backoff from a **1-second** base, with jitter to spread retries. 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/products/matcher/configuration/matcher-exception-routing" horizontal>
  Configure how exceptions trigger webhook events.
</Card>

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