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"
    ]
  }
}

Configuring webhooks


Create webhook endpoint

cURL
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

OptionTypeDescription
nameStringDisplay name for the webhook
urlStringTarget URL for event delivery
eventsArrayList of event types to subscribe to
filtersObjectFilter events by attributes
headersObjectCustom headers to include
transformObjectPayload transformation settings
retryObjectRetry policy configuration
enabledBooleanEnable/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"
    }
  }
}

Payload transformation

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

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

TypeDescriptionAction in Matcher
resolvedIssue fixedMark exception RESOLVED
wont_fixWon’t resolveMark exception RESOLVED with note
duplicateDuplicate of anotherLink to existing exception
rejectedCallback rejectedLog error, keep exception open

Monitoring webhooks


List webhook deliveries

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


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