Webhooks enable real-time communication between Matcher and external systems. This guide covers outbound event notifications and inbound resolution callbacks.
Overview
Matcher lets you bidirectional webhook communication:
Outbound webhooks : Matcher notifies external systems when events occur
Inbound callbacks : External systems notify Matcher when actions are taken
When something happens in Matcher, like a new exception or a completed match, it pushes an event to your configured endpoints. 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.
Outbound events
Matcher emits events when significant actions occur in the reconciliation process.
Available events
Event Trigger Typical Use ingestion.completedFile import finished Data pipeline monitoring ingestion.failedFile import failed Error alerting match.createdNew match proposed Audit logging match.confirmedMatch approved Downstream updates match.rejectedMatch declined Investigation triggers match.force_matchedManual force match applied Audit trail, approval workflows match.unmatchedMatch manually reversed Audit trail, correction tracking match_run.startedAutomatic match job started Job monitoring match_run.completedAutomatic match job finished Job monitoring, reporting match_run.failedAutomatic match job failed Error alerting exception.createdNew exception Ticket creation exception.assignedException assigned User notification exception.escalatedException escalated Management alerts exception.resolvedException resolved Status sync exception.sla_warningSLA approaching Proactive alerts exception.sla_breachSLA exceeded Escalation triggers context.closedReconciliation period closed Reporting triggers
Event payload structure
All events follow a consistent structure:
{
"id" : "evt_001" ,
"type" : "exception.created" ,
"timestamp" : "2024-01-20T10:30:00Z" ,
"version" : "1.0" ,
"tenant_id" : "tenant_001" ,
"context_id" : "ctx_abc123" ,
"data" : {
// Event-specific data
},
"metadata" : {
"correlation_id" : "req_xyz789" ,
"source" : "matching_engine"
}
}
Event payloads
Ingestioncompleted
Fired when a batch of transactions is successfully imported.
{
"id" : "evt_ing_001" ,
"type" : "ingestion.completed" ,
"timestamp" : "2024-01-20T10:30:00Z" ,
"data" : {
"job_id" : "job_imp_001" ,
"source_id" : "src_bank456" ,
"source_name" : "Chase Bank" ,
"file_name" : "statement_january.csv" ,
"summary" : {
"total_rows" : 1250 ,
"imported" : 1240 ,
"duplicates_skipped" : 8 ,
"validation_errors" : 2
},
"duration_seconds" : 45 ,
"started_at" : "2024-01-20T10:29:15Z" ,
"completed_at" : "2024-01-20T10:30:00Z"
}
}
Matchconfirmed
Fired when a match is approved (auto or manual).
{
"id" : "evt_mtch_001" ,
"type" : "match.confirmed" ,
"timestamp" : "2024-01-20T10:30:01Z" ,
"data" : {
"match_id" : "match_001" ,
"confidence" : 100 ,
"rule_id" : "rule_exact001" ,
"rule_name" : "Exact Match" ,
"confirmation_type" : "AUTO" ,
"transactions" : [
{
"id" : "txn_bank_001" ,
"source_name" : "Chase Bank" ,
"amount" : 1000.0 ,
"currency" : "USD" ,
"date" : "2024-01-15"
},
{
"id" : "txn_ledger_001" ,
"source_name" : "Main Ledger" ,
"amount" : 1000.0 ,
"currency" : "USD" ,
"date" : "2024-01-15"
}
],
"variance" : {
"amount_diff" : 0 ,
"date_diff_days" : 0
}
}
}
Exceptioncreated
Fired when a new exception is identified.
{
"id" : "evt_exc_001" ,
"type" : "exception.created" ,
"timestamp" : "2024-01-20T10:30:00Z" ,
"data" : {
"exception_id" : "exc_001" ,
"severity" : "HIGH" ,
"reason" : "NO_MATCH_FOUND" ,
"reason_details" : "No matching transaction found within tolerance" ,
"transaction" : {
"id" : "txn_bank_999" ,
"source_id" : "src_bank456" ,
"source_name" : "Chase Bank" ,
"external_id" : "BANK-2024-999" ,
"amount" : 15000.0 ,
"currency" : "USD" ,
"date" : "2024-01-15" ,
"reference" : "Wire transfer #7890"
},
"candidates" : [
{
"transaction_id" : "txn_ledger_777" ,
"confidence" : 55 ,
"rejection_reason" : "Below 60% threshold"
}
],
"sla" : {
"due_at" : "2024-01-21T10:30:00Z" ,
"hours_remaining" : 24
}
}
}
Exceptionresolved
Fired when an exception is resolved.
{
"id" : "evt_exc_002" ,
"type" : "exception.resolved" ,
"timestamp" : "2024-01-20T14:30:00Z" ,
"data" : {
"exception_id" : "exc_001" ,
"resolution" : {
"type" : "FORCE_MATCH" ,
"match_id" : "match_forced_001" ,
"resolved_by" : "user_123" ,
"notes" : "Verified: amount difference is bank wire fee"
},
"time_to_resolution" : {
"hours" : 4 ,
"within_sla" : true
}
}
}
Matchforcematched
Fired when a user manually forces a match between transactions.
{
"id" : "evt_force_001" ,
"type" : "match.force_matched" ,
"timestamp" : "2024-01-20T14:30:00Z" ,
"data" : {
"match_id" : "match_forced_001" ,
"exception_id" : "exc_001" ,
"forced_by" : "user_123" ,
"reason" : "Amount difference is documented bank fee" ,
"notes" : "Verified with bank statement showing $250 wire fee deducted" ,
"transactions" : [
{
"id" : "txn_bank_999" ,
"source_name" : "Chase Bank" ,
"amount" : 15000.0 ,
"currency" : "USD" ,
"date" : "2024-01-15"
},
{
"id" : "txn_ledger_777" ,
"source_name" : "Main Ledger" ,
"amount" : 15250.0 ,
"currency" : "USD" ,
"date" : "2024-01-14"
}
],
"variance" : {
"amount_diff" : 250.0 ,
"amount_variance_percent" : 1.67 ,
"date_diff_days" : 1
},
"requires_approval" : true ,
"approval_threshold" : 10000.0
}
}
Force match events are commonly used to trigger approval workflows when the variance exceeds company thresholds.
Matchunmatched
Fired when a previously confirmed match is reversed.
{
"id" : "evt_unmatch_001" ,
"type" : "match.unmatched" ,
"timestamp" : "2024-01-20T15:45:00Z" ,
"data" : {
"match_id" : "match_001" ,
"unmatched_by" : "user_456" ,
"reason" : "Incorrect match - transactions belong to different invoices" ,
"notes" : "Bank txn is for Invoice #1234, ledger txn is for Invoice #5678" ,
"original_match" : {
"confirmed_at" : "2024-01-15T10:00:00Z" ,
"confirmed_by" : "system" ,
"confidence" : 85 ,
"rule_name" : "Tolerance Match"
},
"transactions" : [
{
"id" : "txn_bank_001" ,
"source_name" : "Chase Bank" ,
"amount" : 1000.0 ,
"currency" : "USD"
},
{
"id" : "txn_ledger_001" ,
"source_name" : "Main Ledger" ,
"amount" : 1005.0 ,
"currency" : "USD"
}
],
"action" : {
"exceptions_created" : [
"exc_new_001" ,
"exc_new_002"
],
"status" : "Both transactions returned to unmatched pool"
}
}
}
Unmatching a confirmed match creates new exceptions for both transactions and triggers a full audit log entry.
Matchruncompleted
Fired when an automatic or manual match run finishes.
{
"id" : "evt_run_001" ,
"type" : "match_run.completed" ,
"timestamp" : "2024-01-20T06:05:23Z" ,
"data" : {
"run_id" : "run_001" ,
"context_id" : "ctx_abc123" ,
"context_name" : "Daily Bank Reconciliation" ,
"trigger" : "SCHEDULED" ,
"started_at" : "2024-01-20T06:00:00Z" ,
"completed_at" : "2024-01-20T06:05:23Z" ,
"duration_seconds" : 323 ,
"statistics" : {
"transactions_processed" : 1250 ,
"matches_found" : 1180 ,
"matches_confirmed" : 1120 ,
"matches_pending_review" : 60 ,
"exceptions_created" : 70 ,
"by_confidence" : {
"high_90_100" : 1120 ,
"medium_60_89" : 60 ,
"low_0_59" : 0
}
},
"performance" : {
"transactions_per_second" : 3.87 ,
"avg_rule_evaluation_ms" : 12.5
}
}
}
Slawarning / slabreach
Fired when SLA thresholds are reached.
{
"id" : "evt_sla_001" ,
"type" : "exception.sla_warning" ,
"timestamp" : "2024-01-20T18:30:00Z" ,
"data" : {
"exception_id" : "exc_001" ,
"severity" : "HIGH" ,
"sla" : {
"due_at" : "2024-01-21T10:30:00Z" ,
"hours_remaining" : 16 ,
"percent_remaining" : 25
},
"assigned_to" : "user_123" ,
"escalation_path" : [
"user_manager" ,
"user_director"
]
}
}
Configuring webhooks
Create webhook endpoint
curl -X POST "https://api.matcher.example.com/v1/webhooks" \
-H "Authorization: Bearer $TOKEN " \
-H "Content-Type: application/json" \
-d '{
"name": "Slack Notifications",
"url": "https://hooks.slack.com/services/T00/B00/XXX",
"events": [
"exception.created",
"exception.sla_warning",
"exception.sla_breach"
],
"filters": {
"severity": [
"CRITICAL",
"HIGH"
]
},
"headers": {
"Content-Type": "application/json"
},
"transform": {
"enabled": true,
"template": "slack"
}
}'
Response
{
"id" : "wh_001" ,
"name" : "Slack Notifications" ,
"url" : "https://hooks.slack.com/services/T00/B00/XXX" ,
"events" : [
"exception.created" ,
"exception.sla_warning" ,
"exception.sla_breach"
],
"filters" : {
"severity" : [
"CRITICAL" ,
"HIGH"
]
},
"status" : "ACTIVE" ,
"secret" : "whsec_abc123..." ,
"created_at" : "2024-01-15T10:00:00Z"
}
Webhook configuration options
Option Type Description nameString Display name for the webhook urlString Target URL for event delivery eventsArray List of event types to subscribe to filtersObject Filter events by attributes headersObject Custom headers to include transformObject Payload transformation settings retryObject Retry policy configuration enabledBoolean Enable/disable the webhook
Event filters
Filter which events trigger the webhook:
{
"filters" : {
"context_id" : [
"ctx_abc123" ,
"ctx_def456"
],
"source_id" : [
"src_bank456"
],
"severity" : [
"CRITICAL" ,
"HIGH"
],
"amount_min" : 10000 ,
"metadata" : {
"region" : "US"
}
}
}
Transform events to match target system format.
Slack template:
{
"transform" : {
"enabled" : true ,
"template" : "slack" ,
"options" : {
"channel" : "#reconciliation-alerts" ,
"username" : "Matcher Bot" ,
"icon_emoji" : ":money_with_wings:"
}
}
}
Custom template:
{
"transform" : {
"enabled" : true ,
"template" : "custom" ,
"body" : {
"alert_type" : "{{event.type}}" ,
"amount" : "{{data.transaction.amount}}" ,
"currency" : "{{data.transaction.currency}}" ,
"description" : "Exception {{data.exception_id}} created for ${{data.transaction.amount}}"
}
}
}
Webhook security
Signature verification
All webhook deliveries include a signature header for verification:
X-Matcher-Signature: sha256=abc123...
X-Matcher-Timestamp: 1705758600
Verification process:
Concatenate timestamp and body: {timestamp}.{body}
Compute HMAC-SHA256 using webhook secret
Compare with signature header
Example (Node.js):
const crypto = require ( 'crypto' );
function verifyWebhook ( payload , signature , timestamp , secret ) {
const signedPayload = ` ${ timestamp } . ${ payload } ` ;
const expectedSignature = crypto
. createHmac ( 'sha256' , secret )
. update ( signedPayload )
. digest ( 'hex' );
return `sha256= ${ expectedSignature } ` === signature ;
}
Example (Python):
import hmac
import hashlib
def verify_webhook ( payload , signature , timestamp , secret ):
signed_payload = f " { timestamp } . { payload } "
expected = hmac.new(
secret.encode(),
signed_payload.encode(),
hashlib.sha256
).hexdigest()
return f "sha256= { expected } " == signature
Ip allowlisting
Matcher sends webhooks from specific IP ranges:
Configure your firewall to allow these ranges.
TLS requirements
Minimum TLS 1.2
Valid SSL certificate required
Self-signed certificates not supported in production
Retry logic
Failed webhook deliveries are retried with exponential backoff.
Default retry policy
Attempt Delay Total Wait 1 Immediate 0s 2 5 seconds 5s 3 30 seconds 35s 4 2 minutes 2m 35s 5 10 minutes 12m 35s 6 1 hour 1h 12m 35s
Custom retry configuration
{
"retry" : {
"max_attempts" : 5 ,
"initial_delay_seconds" : 10 ,
"max_delay_seconds" : 3600 ,
"backoff_multiplier" : 2
}
}
Retry conditions
Retries occur for:
HTTP 5xx responses
Connection timeouts
DNS resolution failures
No retry for:
HTTP 4xx responses (except 429)
Invalid SSL certificates
Webhook disabled
Resolution callbacks
External systems can notify Matcher when exceptions are resolved externally.
Callback endpoint
POST https://api.matcher.example.com/v1/callbacks/resolution
Callback payload
{
"integration_id" : "int_jira_001" ,
"external_id" : "RECON-123" ,
"exception_id" : "exc_001" ,
"resolution" : {
"status" : "resolved" ,
"resolution_type" : "fixed" ,
"notes" : "Resolved via JIRA ticket RECON-123" ,
"resolved_by" : "[email protected] " ,
"resolved_at" : "2024-01-20T14:30:00Z" ,
"custom_fields" : {
"ticket_url" : "https://jira.company.com/browse/RECON-123"
}
}
}
Callback authentication
API Key:
curl -X POST "https://api.matcher.example.com/v1/callbacks/resolution" \
-H "X-API-Key: callback_api_key_001" \
-H "Content-Type: application/json" \
-d '{"integration_id": "int_jira_001", ...}'
HMAC Signature:
curl -X POST "https://api.matcher.example.com/v1/callbacks/resolution" \
-H "X-Callback-Signature: sha256=abc123..." \
-H "X-Callback-Timestamp: 1705758600" \
-H "Content-Type: application/json" \
-d '{"integration_id": "int_jira_001", ...}'
Resolution types
Type Description Action in Matcher resolvedIssue fixed Mark exception RESOLVED wont_fixWon’t resolve Mark exception RESOLVED with note duplicateDuplicate of another Link to existing exception rejectedCallback rejected Log error, keep exception open
Monitoring webhooks
List webhook deliveries
curl -X GET "https://api.matcher.example.com/v1/webhooks/wh_001/deliveries?limit=20" \
-H "Authorization: Bearer $TOKEN "
Response
{
"deliveries" : [
{
"id" : "del_001" ,
"event_id" : "evt_exc_001" ,
"event_type" : "exception.created" ,
"status" : "DELIVERED" ,
"attempts" : 1 ,
"response_code" : 200 ,
"response_time_ms" : 145 ,
"delivered_at" : "2024-01-20T10:30:01Z"
},
{
"id" : "del_002" ,
"event_id" : "evt_exc_002" ,
"event_type" : "exception.created" ,
"status" : "FAILED" ,
"attempts" : 6 ,
"last_error" : "Connection timeout" ,
"next_retry" : "2024-01-20T11:30:00Z"
}
],
"summary" : {
"total" : 1250 ,
"delivered" : 1245 ,
"failed" : 3 ,
"pending" : 2
}
}
Webhook health
curl -X GET "https://api.matcher.example.com/v1/webhooks/wh_001/health" \
-H "Authorization: Bearer $TOKEN "
Response
{
"webhook_id" : "wh_001" ,
"status" : "HEALTHY" ,
"metrics" : {
"deliveries_24h" : 450 ,
"success_rate" : 99.5 ,
"average_latency_ms" : 125 ,
"failures_24h" : 2
},
"last_delivery" : {
"timestamp" : "2024-01-20T10:30:01Z" ,
"status" : "DELIVERED"
}
}
Retry failed deliveries
curl -X POST "https://api.matcher.example.com/v1/webhooks/wh_001/deliveries/del_002/retry" \
-H "Authorization: Bearer $TOKEN "
Testing webhooks
Send test event
curl -X POST "https://api.matcher.example.com/v1/webhooks/wh_001/test" \
-H "Authorization: Bearer $TOKEN " \
-H "Content-Type: application/json" \
-d '{
"event_type": "exception.created"
}'
Response
{
"test_id" : "test_001" ,
"webhook_id" : "wh_001" ,
"event_type" : "exception.created" ,
"delivery" : {
"status" : "DELIVERED" ,
"response_code" : 200 ,
"response_body" : "OK" ,
"response_time_ms" : 95
}
}
Webhook logs
View detailed logs for troubleshooting:
curl -X GET "https://api.matcher.example.com/v1/webhooks/wh_001/deliveries/del_002/logs" \
-H "Authorization: Bearer $TOKEN "
Response
{
"delivery_id" : "del_002" ,
"logs" : [
{
"timestamp" : "2024-01-20T10:30:00Z" ,
"attempt" : 1 ,
"action" : "SEND" ,
"details" : {
"url" : "https://..." ,
"method" : "POST"
}
},
{
"timestamp" : "2024-01-20T10:30:05Z" ,
"attempt" : 1 ,
"action" : "TIMEOUT" ,
"details" : {
"error" : "Connection timeout after 5000ms"
}
},
{
"timestamp" : "2024-01-20T10:30:10Z" ,
"attempt" : 2 ,
"action" : "RETRY_SCHEDULED" ,
"details" : {
"next_attempt" : "2024-01-20T10:30:15Z"
}
}
]
}
Best practices
Verify webhook signatures
Always verify the HMAC signature before processing webhook payloads. This prevents spoofed requests.
Return a 2xx response within 5 seconds. Process the event asynchronously if needed.
Handle duplicates idempotently
Events may be delivered more than once. Use the event ID to deduplicate.
Set up alerts for webhook failure rates. Investigate persistent failures promptly.
Subscribe only to events you need. Filtering reduces noise and processing load.
Use the test endpoint to verify your webhook handler works correctly before enabling in production.
Next steps