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"
]
}
}
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 systems like JIRA or ServiceNow.
Process a callback
curl -X POST "https://api.matcher.example.com/v1/exceptions/{exceptionId}/callback" \
-H "Authorization: Bearer $TOKEN " \
-H "X-Idempotency-Key: callback-jira-1234" \
-H "Content-Type: application/json" \
-d '{
"externalSystem": "JIRA",
"externalIssueId": "RECON-1234",
"status": "RESOLVED",
"resolutionNotes": "Verified: amount difference is expected bank wire fee",
"assignee": "john.doe@company.com"
}'
Response
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.
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
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