Skip to content

Multi-Provider Monitoring Dashboard

This use case demonstrates the fan-in pattern—aggregating webhooks from multiple providers (GitHub, Stripe, and Shopify) into a single monitoring dashboard. Each provider's payload is normalized into a common schema using dedicated transforms, giving you a unified view of events across your entire stack.

Architecture

mermaid
flowchart LR
    GH[GitHub Webhooks] --> S1[Source: GitHub]
    ST[Stripe Webhooks] --> S2[Source: Stripe]
    SH[Shopify Webhooks] --> S3[Source: Shopify]
    S1 --> R1[Route: GitHub → Monitor]
    S2 --> R2[Route: Stripe → Monitor]
    S3 --> R3[Route: Shopify → Monitor]
    R1 --> |Transform: Normalize| D1[Monitoring API]
    R2 --> |Transform: Normalize| D1
    R3 --> |Transform: Normalize| D1

Flow:

  1. Three webhook sources receive events from different providers
  2. Each source verifies signatures using provider-specific methods
  3. Routes connect each source to the shared monitoring destination
  4. Provider-specific transforms normalize payloads into a common schema
  5. The monitoring API receives a consistent event format regardless of provider

Common Normalized Schema

All provider events are transformed into this unified format:

json
{
  "provider": "github | stripe | shopify",
  "event_type": "normalized.event.name",
  "severity": "info | warning | error | critical",
  "summary": "Human-readable description of the event",
  "timestamp": "2026-02-11T14:30:00Z",
  "metadata": {
    "provider_event_id": "original event ID",
    "provider_event_type": "original event type",
    "resource_id": "primary resource identifier",
    "resource_url": "link to resource in provider dashboard"
  }
}

This schema enables:

  • Unified dashboards across all providers
  • Consistent alerting rules regardless of source
  • Simplified querying and aggregation
  • Easy addition of new providers without changing the dashboard

Step 1: Create Sources

GitHub Source

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/sources \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "GitHub Production",
    "slug": "github-monitor",
    "description": "GitHub webhooks for monitoring dashboard",
    "verificationConfig": {
      "type": "github",
      "secret": "your_github_webhook_secret"
    }
  }'

Response:

json
{
  "data": {
    "id": "src_gh_monitor01",
    "name": "GitHub Production",
    "slug": "github-monitor",
    "url": "https://api.hookbase.app/ingest/your-org/github-monitor",
    "verificationConfig": {
      "type": "github"
    },
    "createdAt": "2026-02-11T10:00:00Z"
  }
}

Stripe Source

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/sources \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Stripe Production",
    "slug": "stripe-monitor",
    "description": "Stripe webhooks for monitoring dashboard",
    "verificationConfig": {
      "type": "stripe",
      "secret": "whsec_your_stripe_webhook_signing_secret"
    }
  }'

Response:

json
{
  "data": {
    "id": "src_st_monitor01",
    "name": "Stripe Production",
    "slug": "stripe-monitor",
    "url": "https://api.hookbase.app/ingest/your-org/stripe-monitor",
    "verificationConfig": {
      "type": "stripe"
    },
    "createdAt": "2026-02-11T10:00:00Z"
  }
}

Shopify Source

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/sources \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Shopify Production",
    "slug": "shopify-monitor",
    "description": "Shopify webhooks for monitoring dashboard",
    "verificationConfig": {
      "type": "shopify",
      "secret": "your_shopify_webhook_signing_secret"
    }
  }'

Response:

json
{
  "data": {
    "id": "src_sh_monitor01",
    "name": "Shopify Production",
    "slug": "shopify-monitor",
    "url": "https://api.hookbase.app/ingest/your-org/shopify-monitor",
    "verificationConfig": {
      "type": "shopify"
    },
    "createdAt": "2026-02-11T10:00:00Z"
  }
}

TIP

Configure each source URL in the respective provider's webhook settings. You can use the same Hookbase organization for all providers—each source has a unique slug and endpoint.

Step 2: Create the Shared Destination

Create a single monitoring API destination that receives all normalized events:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/destinations \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Monitoring Dashboard API",
    "type": "http",
    "config": {
      "url": "https://monitoring.yourcompany.com/api/events/ingest",
      "method": "POST",
      "headers": {
        "Authorization": "Bearer YOUR_MONITORING_API_KEY",
        "Content-Type": "application/json",
        "X-Source": "hookbase"
      }
    },
    "retryConfig": {
      "maxAttempts": 5,
      "backoffMultiplier": 2,
      "initialInterval": 1000
    }
  }'

Response:

json
{
  "data": {
    "id": "dst_monitor01",
    "name": "Monitoring Dashboard API",
    "type": "http",
    "createdAt": "2026-02-11T10:00:00Z"
  }
}

Step 3: Create Transforms

Each transform normalizes a provider's payload into the common schema.

GitHub Transform

Maps GitHub events (push, pull_request, deployment, issues) to the normalized schema:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/transforms \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "GitHub → Normalized Monitor",
    "type": "jsonata",
    "config": {
      "expression": "(\n  $event := headers.\"x-github-event\";\n  $severity := $event = \"security_advisory\" ? \"critical\" : ($event in [\"deployment_status\", \"workflow_run\"] ? (deployment_status.state = \"failure\" or workflow_run.conclusion = \"failure\" ? \"error\" : \"info\") : ($event = \"pull_request\" ? \"info\" : \"info\"));\n  $summary := $event = \"push\" ? sender.login & \" pushed \" & $string($count(commits)) & \" commit(s) to \" & repository.full_name & \"/\" & $substringAfter(ref, \"refs/heads/\") : ($event = \"pull_request\" ? sender.login & \" \" & action & \" PR #\" & $string(pull_request.number) & \": \" & pull_request.title : ($event = \"deployment_status\" ? \"Deployment to \" & deployment.environment & \" \" & deployment_status.state : ($event = \"issues\" ? sender.login & \" \" & action & \" issue #\" & $string(issue.number) & \": \" & issue.title : $event & \" event from \" & repository.full_name)));\n  $resourceId := $event = \"push\" ? after : ($event = \"pull_request\" ? $string(pull_request.id) : ($event = \"deployment_status\" ? $string(deployment.id) : ($event = \"issues\" ? $string(issue.id) : $string(repository.id))));\n  $resourceUrl := $event = \"pull_request\" ? pull_request.html_url : ($event = \"issues\" ? issue.html_url : repository.html_url);\n  {\n    \"provider\": \"github\",\n    \"event_type\": \"github.\" & $event & ($exists(action) ? \".\" & action : \"\"),\n    \"severity\": $severity,\n    \"summary\": $summary,\n    \"timestamp\": $now(),\n    \"metadata\": {\n      \"provider_event_id\": headers.\"x-github-delivery\",\n      \"provider_event_type\": $event,\n      \"resource_id\": $resourceId,\n      \"resource_url\": $resourceUrl,\n      \"repository\": repository.full_name,\n      \"sender\": sender.login\n    }\n  }\n)"
    }
  }'

Example Output (push event):

json
{
  "provider": "github",
  "event_type": "github.push",
  "severity": "info",
  "summary": "johndoe pushed 3 commit(s) to company/api/main",
  "timestamp": "2026-02-11T14:30:00Z",
  "metadata": {
    "provider_event_id": "abc123-delivery-id",
    "provider_event_type": "push",
    "resource_id": "abc1234def5678",
    "resource_url": "https://github.com/company/api",
    "repository": "company/api",
    "sender": "johndoe"
  }
}

Stripe Transform

Maps Stripe events (payments, subscriptions, invoices) to the normalized schema:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/transforms \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Stripe → Normalized Monitor",
    "type": "jsonata",
    "config": {
      "expression": "(\n  $isFailed := $contains(type, \"failed\");\n  $isDispute := $contains(type, \"dispute\");\n  $severity := $isFailed ? \"error\" : ($isDispute ? \"warning\" : ($contains(type, \"succeeded\") or $contains(type, \"completed\") ? \"info\" : \"info\"));\n  $amount := data.object.amount_total ? data.object.amount_total : data.object.amount;\n  $currency := $uppercase(data.object.currency);\n  $summary := $contains(type, \"payment_intent\") ? \"Payment \" & $substringAfter(type, \"payment_intent.\") & ($amount ? \" - \" & $currency & \" \" & $string($round($amount / 100, 2)) : \"\") : ($contains(type, \"invoice\") ? \"Invoice \" & $substringAfter(type, \"invoice.\") & ($amount ? \" - \" & $currency & \" \" & $string($round($amount / 100, 2)) : \"\") : ($contains(type, \"checkout\") ? \"Checkout session \" & $substringAfter(type, \"checkout.session.\") & ($amount ? \" - \" & $currency & \" \" & $string($round($amount / 100, 2)) : \"\") : ($isDispute ? \"Dispute \" & $substringAfter(type, \"dispute.\") & \" for \" & data.object.reason : type)));\n  {\n    \"provider\": \"stripe\",\n    \"event_type\": \"stripe.\" & $replace(type, \".\", \"_\"),\n    \"severity\": $severity,\n    \"summary\": $summary,\n    \"timestamp\": $fromMillis(created * 1000),\n    \"metadata\": {\n      \"provider_event_id\": id,\n      \"provider_event_type\": type,\n      \"resource_id\": data.object.id,\n      \"resource_url\": \"https://dashboard.stripe.com/\" & ($contains(type, \"invoice\") ? \"invoices/\" : ($contains(type, \"payment_intent\") ? \"payments/\" : ($contains(type, \"customer\") ? \"customers/\" : \"events/\"))) & (data.object.id ? data.object.id : id),\n      \"customer_id\": data.object.customer,\n      \"amount\": $amount,\n      \"currency\": $currency\n    }\n  }\n)"
    }
  }'

Example Output (payment_intent.succeeded):

json
{
  "provider": "stripe",
  "event_type": "stripe.payment_intent_succeeded",
  "severity": "info",
  "summary": "Payment succeeded - USD 49.99",
  "timestamp": "2026-02-11T14:30:00Z",
  "metadata": {
    "provider_event_id": "evt_1234567890",
    "provider_event_type": "payment_intent.succeeded",
    "resource_id": "pi_abc123def456",
    "resource_url": "https://dashboard.stripe.com/payments/pi_abc123def456",
    "customer_id": "cus_abc123",
    "amount": 4999,
    "currency": "USD"
  }
}

Shopify Transform

Maps Shopify events (orders, refunds, inventory) to the normalized schema:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/transforms \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Shopify → Normalized Monitor",
    "type": "jsonata",
    "config": {
      "expression": "(\n  $topic := headers.\"x-shopify-topic\";\n  $isRefund := $contains($topic, \"refund\");\n  $isCancellation := $contains($topic, \"cancelled\");\n  $severity := $isRefund ? \"warning\" : ($isCancellation ? \"warning\" : ($contains($topic, \"orders/create\") ? \"info\" : \"info\"));\n  $total := total_price ? total_price : (amount ? amount : \"0.00\");\n  $summary := $contains($topic, \"orders/create\") ? \"New order #\" & $string(order_number) & \" - $\" & total_price & \" \" & currency : ($contains($topic, \"orders/updated\") ? \"Order #\" & $string(order_number) & \" updated - \" & financial_status : ($isRefund ? \"Refund processed for order #\" & $string(order.order_number) & \" - $\" & $string($sum(refund_line_items.(quantity * $number(line_item.price)))) : ($contains($topic, \"orders/cancelled\") ? \"Order #\" & $string(order_number) & \" cancelled\" : $topic & \" event\")));\n  $resourceId := id ? $string(id) : $string(order.id);\n  $shopDomain := headers.\"x-shopify-shop-domain\";\n  {\n    \"provider\": \"shopify\",\n    \"event_type\": \"shopify.\" & $replace($topic, \"/\", \"_\"),\n    \"severity\": $severity,\n    \"summary\": $summary,\n    \"timestamp\": created_at ? created_at : $now(),\n    \"metadata\": {\n      \"provider_event_id\": headers.\"x-shopify-webhook-id\",\n      \"provider_event_type\": $topic,\n      \"resource_id\": $resourceId,\n      \"resource_url\": \"https://\" & $shopDomain & \"/admin/orders/\" & $resourceId,\n      \"shop_domain\": $shopDomain,\n      \"total_price\": $total,\n      \"currency\": currency ? currency : order.currency\n    }\n  }\n)"
    }
  }'

Example Output (orders/create):

json
{
  "provider": "shopify",
  "event_type": "shopify.orders_create",
  "severity": "info",
  "summary": "New order #1042 - $749.95 USD",
  "timestamp": "2026-02-11T14:30:00-05:00",
  "metadata": {
    "provider_event_id": "b5678-webhook-id",
    "provider_event_type": "orders/create",
    "resource_id": "5678901234",
    "resource_url": "https://your-store.myshopify.com/admin/orders/5678901234",
    "shop_domain": "your-store.myshopify.com",
    "total_price": "749.95",
    "currency": "USD"
  }
}

Step 4: Create Routes

Connect each source to the shared monitoring destination with its provider-specific transform.

GitHub → Monitor

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/routes \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "GitHub → Monitor Dashboard",
    "sourceId": "src_gh_monitor01",
    "destinationId": "dst_monitor01",
    "transformId": "tfm_gh_normalize01",
    "enabled": true
  }'

Stripe → Monitor

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/routes \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Stripe → Monitor Dashboard",
    "sourceId": "src_st_monitor01",
    "destinationId": "dst_monitor01",
    "transformId": "tfm_st_normalize01",
    "enabled": true
  }'

Shopify → Monitor

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/routes \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Shopify → Monitor Dashboard",
    "sourceId": "src_sh_monitor01",
    "destinationId": "dst_monitor01",
    "transformId": "tfm_sh_normalize01",
    "enabled": true
  }'

Step 5: Test the Pipeline

Send test payloads from each provider to verify normalization.

Test GitHub Event

bash
curl -X POST https://api.hookbase.app/ingest/your-org/github-monitor \
  -H "Content-Type: application/json" \
  -H "X-GitHub-Event: push" \
  -H "X-Hub-Signature-256: sha256=test_signature" \
  -H "X-GitHub-Delivery: test-delivery-gh-001" \
  -d '{
    "ref": "refs/heads/main",
    "after": "abc1234def5678",
    "commits": [
      {"id": "abc1234", "message": "Fix payment flow"},
      {"id": "def5678", "message": "Update tests"},
      {"id": "ghi9012", "message": "Bump version"}
    ],
    "sender": {
      "login": "johndoe"
    },
    "repository": {
      "full_name": "company/api",
      "html_url": "https://github.com/company/api"
    }
  }'

Test Stripe Event

bash
curl -X POST https://api.hookbase.app/ingest/your-org/stripe-monitor \
  -H "Content-Type: application/json" \
  -H "Stripe-Signature: t=1234567890,v1=test_signature" \
  -d '{
    "id": "evt_test_stripe_001",
    "type": "payment_intent.succeeded",
    "created": 1707649200,
    "data": {
      "object": {
        "id": "pi_abc123def456",
        "amount": 4999,
        "currency": "usd",
        "customer": "cus_abc123",
        "status": "succeeded"
      }
    }
  }'

Test Shopify Event

bash
curl -X POST https://api.hookbase.app/ingest/your-org/shopify-monitor \
  -H "Content-Type: application/json" \
  -H "X-Shopify-Topic: orders/create" \
  -H "X-Shopify-Hmac-Sha256: test_signature" \
  -H "X-Shopify-Shop-Domain: your-store.myshopify.com" \
  -H "X-Shopify-Webhook-Id: test-webhook-sh-001" \
  -d '{
    "id": 5678901234,
    "order_number": 1042,
    "total_price": "749.95",
    "currency": "USD",
    "financial_status": "paid",
    "customer": {
      "id": 7890123456,
      "first_name": "Jane",
      "last_name": "Smith",
      "email": "[email protected]"
    },
    "line_items": [
      {
        "title": "Widget Pro",
        "quantity": 2,
        "price": "299.99"
      }
    ],
    "created_at": "2026-02-11T14:30:00-05:00"
  }'

Verify Deliveries:

bash
curl https://api.hookbase.app/api/organizations/{orgId}/deliveries?limit=10 \
  -H "Authorization: Bearer YOUR_TOKEN"

You should see 3 deliveries to the monitoring destination, each with a normalized payload matching the common schema.

Adding a New Provider

The fan-in pattern makes it straightforward to add new providers. For example, to add Twilio:

  1. Create a source with Twilio signature verification
  2. Create a transform that maps Twilio payloads to the common schema
  3. Create a route connecting the source to the existing monitoring destination

No changes needed to the destination or dashboard—the normalized schema handles it.

bash
# 1. Source
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/sources \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Twilio Production",
    "slug": "twilio-monitor",
    "verificationConfig": {
      "type": "twilio",
      "secret": "your_twilio_auth_token"
    }
  }'

# 2. Transform
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/transforms \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Twilio → Normalized Monitor",
    "type": "jsonata",
    "config": {
      "expression": "{\n  \"provider\": \"twilio\",\n  \"event_type\": \"twilio.\" & EventType,\n  \"severity\": ErrorCode ? \"error\" : \"info\",\n  \"summary\": \"SMS \" & MessageStatus & \" - \" & To,\n  \"timestamp\": $now(),\n  \"metadata\": {\n    \"provider_event_id\": MessageSid,\n    \"provider_event_type\": EventType,\n    \"resource_id\": MessageSid,\n    \"resource_url\": \"https://console.twilio.com/develop/sms/logs/\" & MessageSid\n  }\n}"
    }
  }'

# 3. Route
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/routes \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Twilio → Monitor Dashboard",
    "sourceId": "src_tw_monitor01",
    "destinationId": "dst_monitor01",
    "transformId": "tfm_tw_normalize01",
    "enabled": true
  }'

Production Considerations

1. Enable Deduplication on All Sources

Prevent duplicate events from any provider:

bash
# Apply to each source
for SOURCE_ID in src_gh_monitor01 src_st_monitor01 src_sh_monitor01; do
  curl -X PATCH https://api.hookbase.app/api/organizations/{orgId}/sources/$SOURCE_ID \
    -H "Authorization: Bearer YOUR_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "deduplicationConfig": {
        "enabled": true,
        "keyPath": "id",
        "windowSeconds": 86400
      }
    }'
done

2. Configure Circuit Breaker for Monitoring

Protect your monitoring API if it becomes overwhelmed:

bash
curl -X PATCH https://api.hookbase.app/api/organizations/{orgId}/destinations/dst_monitor01 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "circuitBreakerConfig": {
      "enabled": true,
      "failureThreshold": 15,
      "windowSeconds": 60,
      "resetTimeoutSeconds": 120
    }
  }'

3. Set Up Delivery Failure Alerts

Get notified when monitoring events fail to deliver:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/notification-channels \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Monitor Pipeline Alerts",
    "type": "slack",
    "config": {
      "url": "https://hooks.slack.com/services/YOUR/ALERT/URL"
    },
    "events": ["delivery.failed", "destination.circuit_breaker.opened"],
    "filters": {
      "destinationIds": ["dst_monitor01"]
    }
  }'

Released under the MIT License.