Skip to main content
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.
Matcher Webhooks Callbacks

Outbound events


Matcher emits events when significant actions occur in the reconciliation process.

Available events

EventTriggerTypical Use
ingestion.completedFile import finishedData pipeline monitoring
ingestion.failedFile import failedError alerting
match.createdNew match proposedAudit logging
match.confirmedMatch approvedDownstream updates
match.rejectedMatch declinedInvestigation triggers
match.force_matchedManual force match appliedAudit trail, approval workflows
match.unmatchedMatch manually reversedAudit trail, correction tracking
match_run.startedAutomatic match job startedJob monitoring
match_run.completedAutomatic match job finishedJob monitoring, reporting
match_run.failedAutomatic match job failedError alerting
exception.createdNew exceptionTicket creation
exception.assignedException assignedUser notification
exception.escalatedException escalatedManagement alerts
exception.resolvedException resolvedStatus sync
exception.sla_warningSLA approachingProactive alerts
exception.sla_breachSLA exceededEscalation triggers
context.closedReconciliation period closedReporting 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
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

{
  "status": "accepted"
}
API Reference: Process callback
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:
  1. Concatenate timestamp and body: {timestamp}.{body}
  2. Compute HMAC-SHA256 using webhook secret
  3. 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:
52.1.2.0/24
52.1.3.0/24
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

AttemptDelayTotal Wait
1Immediate0s
25 seconds5s
330 seconds35s
42 minutes2m 35s
510 minutes12m 35s
61 hour1h 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


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