Webhooks Integration Guide
Receive real-time notifications when events occur in your Metro 2 account. Webhooks eliminate the need for polling and let your systems react immediately to submissions, rejections, and other events.
1. Overview
Webhooks are HTTP POST requests sent from Metro 2 to your server when specific events occur. Instead of repeatedly calling our API to check for updates, your application receives notifications automatically.
How They Work
- • You register an HTTPS endpoint URL
- • Metro 2 sends a POST with event data as JSON
- • Your server responds with 2xx to acknowledge
- • Failed deliveries are retried automatically
Why Use Webhooks
- • Real-time updates without polling
- • Reduce API calls and stay under rate limits
- • Trigger downstream workflows automatically
- • Monitor submission health proactively
2. Creating Webhook Endpoints
Register a webhook endpoint via the API. You can create multiple endpoints, each listening for different event types:
POST /api/v1/webhooks
Content-Type: application/json
Authorization: Bearer your_api_key
{
"url": "https://your-app.com/webhooks/metro2",
"description": "Production webhook for all events",
"events": [
"submission.completed",
"submission.failed",
"record.rejected",
"batch.completed",
"schedule.triggered",
"cra_response.received",
"record.updated",
"record.disputed"
],
"secret": "whsec_your_signing_secret_here"
}// Response
{
"webhookId": "wh_abc123",
"url": "https://your-app.com/webhooks/metro2",
"events": ["submission.completed", "submission.failed", "..."],
"status": "active",
"createdAt": "2025-03-15T10:00:00Z"
}
// List webhooks
GET /api/v1/webhooks
// Update a webhook
PATCH /api/v1/webhooks/wh_abc123
{
"events": ["submission.completed", "submission.failed"]
}
// Delete a webhook
DELETE /api/v1/webhooks/wh_abc1233. Event Types Reference
| Event | Description |
|---|---|
submission.completed | A submission to one or more bureaus has been successfully processed. |
submission.failed | A submission failed due to validation errors, auth issues, or bureau rejection. |
record.rejected | An individual record was rejected by a bureau during processing. |
batch.completed | An async batch has finished processing all records. |
schedule.triggered | An automated schedule fired and started a new submission. |
cra_response.received | A CRA response file has been received and parsed from a bureau. |
record.updated | A record's fields were modified (via API or dashboard). |
record.disputed | A consumer dispute has been filed against one of your records. |
4. HMAC-SHA256 Signature Verification
Every webhook request includes an X-Metro2-Signature header containing an HMAC-SHA256 signature. Always verify this signature to ensure the request genuinely came from Metro 2.
JavaScript / Node.js
const crypto = require('crypto');
function verifyWebhook(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Express middleware example
app.post('/webhooks/metro2', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-metro2-signature'];
const secret = process.env.WEBHOOK_SECRET;
if (!verifyWebhook(req.body, signature, secret)) {
console.error('Invalid webhook signature');
return res.status(401).send('Unauthorized');
}
const event = JSON.parse(req.body);
console.log(`Received event: ${event.event}`);
// Process the event
handleWebhookEvent(event);
res.status(200).send('OK');
});Python
import hmac
import hashlib
def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
# Flask example
from flask import Flask, request, abort
app = Flask(__name__)
@app.route('/webhooks/metro2', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Metro2-Signature')
secret = os.environ['WEBHOOK_SECRET']
if not verify_webhook(request.data, signature, secret):
abort(401)
event = request.get_json()
print(f"Received event: {event['event']}")
# Process the event
handle_event(event)
return 'OK', 2005. Handling Retries and Idempotency
If your endpoint does not return a 2xx response, Metro 2 will retry the webhook delivery with exponential backoff:
Retry Schedule:
┌────────────┬───────────────────┐
│ Attempt │ Delay │
├────────────┼───────────────────┤
│ 1st retry │ 1 minute │
│ 2nd retry │ 5 minutes │
│ 3rd retry │ 30 minutes │
│ 4th retry │ 2 hours │
│ 5th retry │ 24 hours │
└────────────┴───────────────────┘
After 5 failed retries, the webhook is marked as failed.
You can view failed deliveries in the dashboard.Idempotency
Each webhook delivery includes a unique X-Metro2-Delivery-Id header. Store this ID and check for duplicates before processing. Retries will use the same delivery ID, so you can safely deduplicate.
// Idempotent webhook handler
const processedDeliveries = new Set(); // Use a database in production
app.post('/webhooks/metro2', (req, res) => {
const deliveryId = req.headers['x-metro2-delivery-id'];
// Check for duplicate delivery
if (processedDeliveries.has(deliveryId)) {
console.log(`Duplicate delivery ${deliveryId}, skipping`);
return res.status(200).send('OK');
}
// Process the event
const event = req.body;
handleWebhookEvent(event);
// Mark as processed
processedDeliveries.add(deliveryId);
res.status(200).send('OK');
});6. Testing Webhooks in Sandbox
The sandbox environment supports webhooks with simulated events. You can test your integration end-to-end without affecting production data.
Use a Tunnel for Local Development
If your webhook endpoint is running locally, use a tool like ngrok to expose it to the internet so Metro 2 can deliver events to your local server.
Trigger Test Events
Use the API to send test events to your registered endpoint:
// Send a test event to your webhook endpoint
POST /api/v1/webhooks/wh_abc123/test
Content-Type: application/json
Authorization: Bearer your_sandbox_api_key
{
"event": "submission.completed"
}Check Delivery Logs
View recent webhook deliveries and their responses:
// List recent deliveries for a webhook
GET /api/v1/webhooks/wh_abc123/deliveries?limit=10
// Response
{
"deliveries": [
{
"deliveryId": "del_xyz789",
"event": "submission.completed",
"status": "delivered",
"responseCode": 200,
"duration": 145,
"timestamp": "2025-03-15T10:30:00Z"
}
]
}Related Resources
API Reference
- • POST /api/v1/webhooks — Register endpoint
- • GET /api/v1/webhooks — List endpoints
- • POST /api/v1/webhooks/:id/test — Send test event
- • GET /api/v1/webhooks/:id/deliveries — Delivery logs