Developer CenterGuidesWebhooks Integration

    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_abc123

    3. Event Types Reference

    EventDescription
    submission.completedA submission to one or more bureaus has been successfully processed.
    submission.failedA submission failed due to validation errors, auth issues, or bureau rejection.
    record.rejectedAn individual record was rejected by a bureau during processing.
    batch.completedAn async batch has finished processing all records.
    schedule.triggeredAn automated schedule fired and started a new submission.
    cra_response.receivedA CRA response file has been received and parsed from a bureau.
    record.updatedA record's fields were modified (via API or dashboard).
    record.disputedA 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', 200

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