Skip to content

Multi-Payment Processor Consolidation

Consolidate transaction webhooks from Stripe, Paddle, and Square into a unified format. Events are normalized and routed to a central finance system, with high-value transactions triggering Slack alerts. This fan-in pattern with content-based routing is essential for businesses accepting payments through multiple processors.

Architecture

mermaid
flowchart LR
    Stripe[Stripe Webhooks] --> S1[Source: Stripe]
    Paddle[Paddle Webhooks] --> S2[Source: Paddle]
    Square[Square Webhooks] --> S3[Source: Square]
    S1 --> R1[Route: Stripe → Finance]
    S1 --> R2[Route: Stripe → High-Value Alerts]
    S2 --> R3[Route: Paddle → Finance]
    S2 --> R4[Route: Paddle → High-Value Alerts]
    S3 --> R5[Route: Square → Finance]
    S3 --> R6[Route: Square → High-Value Alerts]
    R1 --> |Transform| D1[Finance API]
    R3 --> |Transform| D1
    R5 --> |Transform| D1
    R2 --> |Transform| D2[Slack #high-value]
    R4 --> |Transform| D2
    R6 --> |Transform| D2

Flow:

  1. Three payment processors send webhooks to their respective Hookbase source endpoints
  2. Each source verifies the provider-specific signature
  3. Routes evaluate events using content-based filters (event type, amount thresholds)
  4. Transforms normalize each provider's payload into a unified transaction schema
  5. Finance system receives consistent records; Slack receives high-value alerts

Key challenge: Stripe uses amounts in cents, Paddle reports totals with tax included as a Merchant of Record (MoR), and Square nests amounts inside a money object. The transforms normalize all of this.

Step 1: Create Sources

Create one source per payment processor with signature verification.

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-payments",
    "description": "Stripe payment webhooks for consolidation",
    "verificationConfig": {
      "type": "stripe",
      "secret": "whsec_your_stripe_webhook_signing_secret"
    }
  }'

Response:

json
{
  "data": {
    "id": "src_stripe01",
    "name": "Stripe Production",
    "slug": "stripe-payments",
    "url": "https://api.hookbase.app/ingest/your-org/stripe-payments",
    "verificationConfig": {
      "type": "stripe"
    },
    "createdAt": "2026-03-01T10:00:00Z"
  }
}

Paddle 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": "Paddle Production",
    "slug": "paddle-payments",
    "description": "Paddle payment webhooks for consolidation",
    "verificationConfig": {
      "type": "hmac",
      "secret": "pdl_your_paddle_webhook_secret",
      "algorithm": "sha256",
      "header": "Paddle-Signature",
      "encoding": "hex"
    }
  }'

Response:

json
{
  "data": {
    "id": "src_paddle01",
    "name": "Paddle Production",
    "slug": "paddle-payments",
    "url": "https://api.hookbase.app/ingest/your-org/paddle-payments",
    "verificationConfig": {
      "type": "hmac"
    },
    "createdAt": "2026-03-01T10:01:00Z"
  }
}

TIP

Paddle is a Merchant of Record (MoR). Its payloads include tax, fees, and payout details that differ from direct processors. The transforms in Step 4 account for this.

Square 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": "Square Production",
    "slug": "square-payments",
    "description": "Square payment webhooks for consolidation",
    "verificationConfig": {
      "type": "hmac",
      "secret": "your_square_webhook_signature_key",
      "algorithm": "sha1",
      "header": "X-Square-Hmacsha256-Signature",
      "encoding": "base64"
    }
  }'

Response:

json
{
  "data": {
    "id": "src_square01",
    "name": "Square Production",
    "slug": "square-payments",
    "url": "https://api.hookbase.app/ingest/your-org/square-payments",
    "verificationConfig": {
      "type": "hmac"
    },
    "createdAt": "2026-03-01T10:02:00Z"
  }
}

Step 2: Create Destinations

Finance API Destination

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

Slack High-Value Alerts Destination

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/destinations \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Slack #high-value-payments",
    "type": "slack",
    "config": {
      "url": "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
    },
    "retryConfig": {
      "maxAttempts": 2,
      "backoffMultiplier": 1.5,
      "initialInterval": 500
    }
  }'

Step 3: Create Routes with Filters

Each source needs two routes: one to the finance system (successful transactions) and one to Slack (high-value only). Filters use event types and amount thresholds for content-based routing.

Stripe Routes

Filter for successful Stripe payments:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/filters \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Stripe Successful Payments",
    "conditions": [
      {
        "field": "type",
        "operator": "in",
        "value": ["checkout.session.completed", "payment_intent.succeeded", "invoice.paid", "charge.succeeded"]
      }
    ],
    "logic": "AND"
  }'

Route: Stripe to Finance:

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 → Finance",
    "sourceId": "src_stripe01",
    "destinationId": "dst_finance01",
    "filterId": "flt_strfin01",
    "transformId": "tfm_strfin01",
    "enabled": true
  }'

Filter for high-value Stripe payments ($500+):

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/filters \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Stripe High-Value Payments",
    "conditions": [
      {
        "field": "type",
        "operator": "in",
        "value": ["checkout.session.completed", "payment_intent.succeeded", "charge.succeeded"]
      },
      {
        "field": "data.object.amount",
        "operator": "gte",
        "value": 50000
      }
    ],
    "logic": "AND"
  }'

TIP

Stripe amounts are in the smallest currency unit (cents for USD). 50000 = $500.00. Adjust the threshold based on your transaction volume and what you consider "high-value."

Route: Stripe to Slack Alerts:

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 → High-Value Alerts",
    "sourceId": "src_stripe01",
    "destinationId": "dst_slack01",
    "filterId": "flt_strhv01",
    "transformId": "tfm_strhv01",
    "enabled": true
  }'

Paddle Routes

Filter for successful Paddle transactions:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/filters \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Paddle Successful Transactions",
    "conditions": [
      {
        "field": "event_type",
        "operator": "in",
        "value": ["transaction.completed", "transaction.paid"]
      }
    ],
    "logic": "AND"
  }'

Route: Paddle to Finance:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/routes \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Paddle → Finance",
    "sourceId": "src_paddle01",
    "destinationId": "dst_finance01",
    "filterId": "flt_pdlfin01",
    "transformId": "tfm_pdlfin01",
    "enabled": true
  }'

Filter for high-value Paddle transactions ($500+):

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/filters \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Paddle High-Value Transactions",
    "conditions": [
      {
        "field": "event_type",
        "operator": "in",
        "value": ["transaction.completed", "transaction.paid"]
      },
      {
        "field": "data.details.totals.grand_total",
        "operator": "gte",
        "value": "500.00"
      }
    ],
    "logic": "AND"
  }'

Route: Paddle to Slack Alerts:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/routes \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Paddle → High-Value Alerts",
    "sourceId": "src_paddle01",
    "destinationId": "dst_slack01",
    "filterId": "flt_pdlhv01",
    "transformId": "tfm_pdlhv01",
    "enabled": true
  }'

Square Routes

Filter for successful Square payments:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/filters \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Square Successful Payments",
    "conditions": [
      {
        "field": "type",
        "operator": "in",
        "value": ["payment.completed", "payment.updated"]
      },
      {
        "field": "data.object.payment.status",
        "operator": "eq",
        "value": "COMPLETED"
      }
    ],
    "logic": "AND"
  }'

Route: Square to Finance:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/routes \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Square → Finance",
    "sourceId": "src_square01",
    "destinationId": "dst_finance01",
    "filterId": "flt_sqrfin01",
    "transformId": "tfm_sqrfin01",
    "enabled": true
  }'

Filter for high-value Square payments ($500+):

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/filters \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Square High-Value Payments",
    "conditions": [
      {
        "field": "type",
        "operator": "in",
        "value": ["payment.completed", "payment.updated"]
      },
      {
        "field": "data.object.payment.status",
        "operator": "eq",
        "value": "COMPLETED"
      },
      {
        "field": "data.object.payment.amount_money.amount",
        "operator": "gte",
        "value": 50000
      }
    ],
    "logic": "AND"
  }'

TIP

Square amounts are also in the smallest currency unit (cents for USD), matching Stripe's convention. The filter threshold of 50000 equals $500.00.

Route: Square to Slack Alerts:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/routes \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Square → High-Value Alerts",
    "sourceId": "src_square01",
    "destinationId": "dst_slack01",
    "filterId": "flt_sqrhv01",
    "transformId": "tfm_sqrhv01",
    "enabled": true
  }'

Step 4: Add Transforms

Each payment processor uses a different payload structure. The transforms normalize everything into a unified transaction schema that the finance system expects.

All three finance transforms output a unified schema with consistent fields: transaction_id, provider, amount_cents, currency, tax_cents, net_amount_cents, fee_cents, is_merchant_of_record, and timestamp. This lets your finance system process transactions identically regardless of origin.

Stripe Finance Transform

Normalize Stripe payment events into the unified 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 → Unified Transaction",
    "type": "jsonata",
    "config": {
      "expression": "(\n  $amount := data.object.amount_total ? data.object.amount_total : data.object.amount;\n  $currency := $uppercase(data.object.currency);\n  {\n    \"transaction_id\": \"str_\" & data.object.id,\n    \"provider\": \"stripe\",\n    \"provider_event_id\": id,\n    \"customer_id\": data.object.customer,\n    \"amount_cents\": $amount,\n    \"currency\": $currency,\n    \"amount_display\": $string($amount / 100),\n    \"tax_cents\": data.object.total_details.amount_tax ? data.object.total_details.amount_tax : 0,\n    \"net_amount_cents\": $amount - (data.object.total_details.amount_tax ? data.object.total_details.amount_tax : 0),\n    \"fee_cents\": 0,\n    \"is_merchant_of_record\": false,\n    \"payment_method\": data.object.payment_method_types ? data.object.payment_method_types[0] : \"card\",\n    \"status\": \"succeeded\",\n    \"provider_event_type\": type,\n    \"metadata\": data.object.metadata ? data.object.metadata : {},\n    \"timestamp\": $fromMillis(created * 1000)\n  }\n)"
    }
  }'

Example Output (from a Stripe checkout.session.completed):

json
{
  "transaction_id": "str_cs_abc123",
  "provider": "stripe",
  "provider_event_id": "evt_1234567890",
  "customer_id": "cus_abc123",
  "amount_cents": 14999,
  "currency": "USD",
  "amount_display": "149.99",
  "tax_cents": 1200,
  "net_amount_cents": 13799,
  "fee_cents": 0,
  "is_merchant_of_record": false,
  "payment_method": "card",
  "status": "succeeded",
  "provider_event_type": "checkout.session.completed",
  "metadata": {
    "plan": "pro",
    "annual": "true"
  },
  "timestamp": "2026-03-01T10:00:00.000Z"
}

Paddle Finance Transform

Paddle is a Merchant of Record -- it collects payments, handles taxes, and remits a payout. The transform extracts net earnings alongside the gross total:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/transforms \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Paddle → Unified Transaction",
    "type": "jsonata",
    "config": {
      "expression": "(\n  $total := $number(data.details.totals.grand_total);\n  $tax := $number(data.details.totals.tax);\n  $fee := $number(data.details.totals.fee ? data.details.totals.fee : \"0\");\n  $earnings := $number(data.details.totals.earnings ? data.details.totals.earnings : $string($total - $tax - $fee));\n  $currency := $uppercase(data.currency_code);\n  {\n    \"transaction_id\": \"pdl_\" & data.id,\n    \"provider\": \"paddle\",\n    \"provider_event_id\": event_id,\n    \"customer_id\": data.customer_id,\n    \"amount_cents\": $round($total * 100),\n    \"currency\": $currency,\n    \"amount_display\": $string($total),\n    \"tax_cents\": $round($tax * 100),\n    \"net_amount_cents\": $round($earnings * 100),\n    \"fee_cents\": $round($fee * 100),\n    \"is_merchant_of_record\": true,\n    \"payment_method\": data.payments[0].method_details.type ? data.payments[0].method_details.type : \"unknown\",\n    \"status\": \"succeeded\",\n    \"provider_event_type\": event_type,\n    \"metadata\": data.custom_data ? data.custom_data : {},\n    \"timestamp\": occurred_at\n  }\n)"
    }
  }'

WARNING

Paddle reports amounts as decimal strings (e.g., "149.99") rather than cents. The transform converts using $round($total * 100) to avoid floating-point issues. The is_merchant_of_record: true flag lets your finance system distinguish MoR transactions.

Example Output (from a Paddle transaction.completed):

json
{
  "transaction_id": "pdl_txn_01h8bxpvx2T9XYZ",
  "provider": "paddle",
  "provider_event_id": "ntf_01h8bxqyz3ABC",
  "customer_id": "ctm_01h7mrr5j8DEF",
  "amount_cents": 14999,
  "currency": "USD",
  "amount_display": "149.99",
  "tax_cents": 2400,
  "net_amount_cents": 11249,
  "fee_cents": 1350,
  "is_merchant_of_record": true,
  "payment_method": "card",
  "status": "succeeded",
  "provider_event_type": "transaction.completed",
  "metadata": {
    "plan": "pro",
    "source": "website"
  },
  "timestamp": "2026-03-01T10:05:00.000000Z"
}

Square Finance Transform

Square nests amounts inside a money object with separate amount and currency fields:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/transforms \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Square → Unified Transaction",
    "type": "jsonata",
    "config": {
      "expression": "(\n  $payment := data.object.payment;\n  $amount := $payment.amount_money.amount;\n  $currency := $uppercase($payment.amount_money.currency);\n  $tip := $payment.tip_money.amount ? $payment.tip_money.amount : 0;\n  $fee := $payment.processing_fee ? $sum($payment.processing_fee.amount_money.amount) : 0;\n  {\n    \"transaction_id\": \"sqr_\" & $payment.id,\n    \"provider\": \"square\",\n    \"provider_event_id\": event_id ? event_id : $payment.id,\n    \"customer_id\": $payment.customer_id ? $payment.customer_id : \"anonymous\",\n    \"amount_cents\": $amount,\n    \"currency\": $currency,\n    \"amount_display\": $string($amount / 100),\n    \"tax_cents\": $payment.tax_money.amount ? $payment.tax_money.amount : 0,\n    \"net_amount_cents\": $amount - $fee,\n    \"fee_cents\": $fee,\n    \"is_merchant_of_record\": false,\n    \"payment_method\": $payment.source_type ? $lowercase($payment.source_type) : \"card\",\n    \"status\": \"succeeded\",\n    \"provider_event_type\": type,\n    \"metadata\": {\n      \"location_id\": $payment.location_id,\n      \"tip_cents\": $tip\n    },\n    \"timestamp\": $payment.created_at\n  }\n)"
    }
  }'

Example Output (from a Square payment.completed):

json
{
  "transaction_id": "sqr_KkAkhdMYPBJNgD",
  "provider": "square",
  "provider_event_id": "evt_sq_abc123",
  "customer_id": "VDKXEEKPJN48M",
  "amount_cents": 15500,
  "currency": "USD",
  "amount_display": "155",
  "tax_cents": 1200,
  "net_amount_cents": 15050,
  "fee_cents": 450,
  "is_merchant_of_record": false,
  "payment_method": "card",
  "status": "succeeded",
  "provider_event_type": "payment.completed",
  "metadata": {
    "location_id": "LPNKH7GXR3KH8",
    "tip_cents": 500
  },
  "timestamp": "2026-03-01T10:10:00.000Z"
}

Slack High-Value Alert Transforms

Each processor needs its own Slack transform to extract the correct amount fields. All produce the same Block Kit structure.

Stripe High-Value Slack Transform:

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 → High-Value Slack Alert",
    "type": "jsonata",
    "config": {
      "expression": "(\n  $amount := data.object.amount_total ? data.object.amount_total : data.object.amount;\n  $currency := $uppercase(data.object.currency);\n  {\n    \"attachments\": [\n      {\n        \"color\": \"#635bff\",\n        \"blocks\": [\n          {\n            \"type\": \"header\",\n            \"text\": {\n              \"type\": \"plain_text\",\n              \"text\": \":moneybag: High-Value Payment — Stripe\"\n            }\n          },\n          {\n            \"type\": \"section\",\n            \"fields\": [\n              {\n                \"type\": \"mrkdwn\",\n                \"text\": \"*Amount:*\\n\" & $currency & \" \" & $string($amount / 100)\n              },\n              {\n                \"type\": \"mrkdwn\",\n                \"text\": \"*Customer:*\\n\" & data.object.customer\n              },\n              {\n                \"type\": \"mrkdwn\",\n                \"text\": \"*Payment Method:*\\n\" & (data.object.payment_method_types ? data.object.payment_method_types[0] : \"card\")\n              },\n              {\n                \"type\": \"mrkdwn\",\n                \"text\": \"*Event:*\\n\" & type\n              }\n            ]\n          },\n          {\n            \"type\": \"context\",\n            \"elements\": [\n              {\n                \"type\": \"mrkdwn\",\n                \"text\": \"Provider: Stripe | Event: \" & id & \" | <https://dashboard.stripe.com/events/\" & id & \"|View in Stripe>\"\n              }\n            ]\n          }\n        ]\n      }\n    ]\n  }\n)"
    }
  }'

Paddle High-Value Slack Transform:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/transforms \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Paddle → High-Value Slack Alert",
    "type": "jsonata",
    "config": {
      "expression": "(\n  $total := data.details.totals.grand_total;\n  $currency := $uppercase(data.currency_code);\n  $earnings := data.details.totals.earnings ? data.details.totals.earnings : $total;\n  {\n    \"attachments\": [\n      {\n        \"color\": \"#3b82f6\",\n        \"blocks\": [\n          {\n            \"type\": \"header\",\n            \"text\": {\n              \"type\": \"plain_text\",\n              \"text\": \":moneybag: High-Value Payment — Paddle (MoR)\"\n            }\n          },\n          {\n            \"type\": \"section\",\n            \"fields\": [\n              {\n                \"type\": \"mrkdwn\",\n                \"text\": \"*Gross Amount:*\\n\" & $currency & \" \" & $total\n              },\n              {\n                \"type\": \"mrkdwn\",\n                \"text\": \"*Net Earnings:*\\n\" & $currency & \" \" & $earnings\n              },\n              {\n                \"type\": \"mrkdwn\",\n                \"text\": \"*Customer:*\\n\" & data.customer_id\n              },\n              {\n                \"type\": \"mrkdwn\",\n                \"text\": \"*Tax (incl.):*\\n\" & $currency & \" \" & data.details.totals.tax\n              }\n            ]\n          },\n          {\n            \"type\": \"context\",\n            \"elements\": [\n              {\n                \"type\": \"mrkdwn\",\n                \"text\": \"Provider: Paddle (Merchant of Record) | Transaction: \" & data.id\n              }\n            ]\n          }\n        ]\n      }\n    ]\n  }\n)"
    }
  }'

Square High-Value Slack Transform:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/transforms \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Square → High-Value Slack Alert",
    "type": "jsonata",
    "config": {
      "expression": "(\n  $payment := data.object.payment;\n  $amount := $payment.amount_money.amount;\n  $currency := $uppercase($payment.amount_money.currency);\n  {\n    \"attachments\": [\n      {\n        \"color\": \"#006aff\",\n        \"blocks\": [\n          {\n            \"type\": \"header\",\n            \"text\": {\n              \"type\": \"plain_text\",\n              \"text\": \":moneybag: High-Value Payment — Square\"\n            }\n          },\n          {\n            \"type\": \"section\",\n            \"fields\": [\n              {\n                \"type\": \"mrkdwn\",\n                \"text\": \"*Amount:*\\n\" & $currency & \" \" & $string($amount / 100)\n              },\n              {\n                \"type\": \"mrkdwn\",\n                \"text\": \"*Customer:*\\n\" & ($payment.customer_id ? $payment.customer_id : \"Walk-in\")\n              },\n              {\n                \"type\": \"mrkdwn\",\n                \"text\": \"*Location:*\\n\" & $payment.location_id\n              },\n              {\n                \"type\": \"mrkdwn\",\n                \"text\": \"*Source:*\\n\" & ($payment.source_type ? $payment.source_type : \"CARD\")\n              }\n            ]\n          },\n          {\n            \"type\": \"context\",\n            \"elements\": [\n              {\n                \"type\": \"mrkdwn\",\n                \"text\": \"Provider: Square | Payment: \" & $payment.id & \" | <https://squareup.com/dashboard/sales/transactions/\" & $payment.id & \"|View in Square>\"\n              }\n            ]\n          }\n        ]\n      }\n    ]\n  }\n)"
    }
  }'

Step 5: Test the Pipeline

Send a test payload for each processor to verify routing and transformation.

Test Stripe

bash
curl -X POST https://api.hookbase.app/ingest/your-org/stripe-payments \
  -H "Content-Type: application/json" \
  -H "Stripe-Signature: t=1234567890,v1=test_signature" \
  -d '{
    "id": "evt_test_stripe_001",
    "type": "checkout.session.completed",
    "api_version": "2024-11-20.acacia",
    "created": 1709287200,
    "data": {
      "object": {
        "id": "cs_test_stripe_001",
        "customer": "cus_test_001",
        "subscription": "sub_test_001",
        "amount_total": 75000,
        "currency": "usd",
        "status": "complete",
        "payment_method_types": ["card"],
        "invoice": "in_test_001",
        "total_details": {
          "amount_tax": 6000
        },
        "metadata": {
          "plan": "business",
          "annual": "true"
        }
      }
    }
  }'

Test Paddle

bash
curl -X POST https://api.hookbase.app/ingest/your-org/paddle-payments \
  -H "Content-Type: application/json" \
  -H "Paddle-Signature: ts=1234567890;h1=test_signature" \
  -d '{
    "event_id": "ntf_test_paddle_001",
    "event_type": "transaction.completed",
    "occurred_at": "2026-03-01T10:05:00.000000Z",
    "data": {
      "id": "txn_test_paddle_001",
      "customer_id": "ctm_test_001",
      "currency_code": "USD",
      "details": {
        "totals": {
          "grand_total": "750.00",
          "tax": "120.00",
          "fee": "67.50",
          "earnings": "562.50"
        }
      },
      "payments": [
        {
          "method_details": {
            "type": "card"
          }
        }
      ],
      "custom_data": {
        "plan": "business",
        "source": "website"
      }
    }
  }'

Test Square

bash
curl -X POST https://api.hookbase.app/ingest/your-org/square-payments \
  -H "Content-Type: application/json" \
  -H "X-Square-Hmacsha256-Signature: test_signature" \
  -d '{
    "event_id": "evt_test_square_001",
    "type": "payment.completed",
    "data": {
      "object": {
        "payment": {
          "id": "KkAkhdMYtest001",
          "customer_id": "VDKXEEKPTEST",
          "location_id": "LPNKH7GXTEST",
          "amount_money": {
            "amount": 82500,
            "currency": "USD"
          },
          "tip_money": {
            "amount": 1500
          },
          "tax_money": {
            "amount": 6600
          },
          "source_type": "CARD",
          "status": "COMPLETED",
          "created_at": "2026-03-01T10:10:00.000Z"
        }
      }
    }
  }'

TIP

For production testing, use each provider's test/sandbox mode to generate valid signatures.

Verify Deliveries:

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

You should see up to 6 deliveries: 3 to Finance API (unified format) and up to 3 to Slack (amounts >= $500 only). Verify that is_merchant_of_record is true only for Paddle transactions.

Production Considerations

1. Enable Deduplication on All Sources

Prevent double-counting when processors retry delivery:

bash
# Stripe — deduplicate on event ID
curl -X PATCH https://api.hookbase.app/api/organizations/{orgId}/sources/src_stripe01 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "deduplicationConfig": {
      "enabled": true,
      "keyPath": "id",
      "windowSeconds": 86400
    }
  }'
bash
# Paddle — deduplicate on notification ID
curl -X PATCH https://api.hookbase.app/api/organizations/{orgId}/sources/src_paddle01 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "deduplicationConfig": {
      "enabled": true,
      "keyPath": "event_id",
      "windowSeconds": 86400
    }
  }'
bash
# Square — deduplicate on event ID
curl -X PATCH https://api.hookbase.app/api/organizations/{orgId}/sources/src_square01 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "deduplicationConfig": {
      "enabled": true,
      "keyPath": "event_id",
      "windowSeconds": 86400
    }
  }'

2. Set Up Failover for Finance Route

Configure a backup destination to catch failed finance deliveries:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/destinations \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Finance API Backup",
    "type": "http",
    "config": {
      "url": "https://finance-backup.yourcompany.com/api/transactions",
      "method": "POST",
      "headers": {
        "Authorization": "Bearer YOUR_BACKUP_FINANCE_KEY",
        "Content-Type": "application/json"
      }
    }
  }'

Then enable failover on each finance route (repeat for rte_strfin01, rte_pdlfin01, rte_sqrfin01):

bash
curl -X PATCH https://api.hookbase.app/api/organizations/{orgId}/routes/rte_strfin01 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "failoverConfig": {
      "enabled": true,
      "destinationId": "dst_finbackup01",
      "triggerAfterAttempts": 3
    }
  }'

3. Configure Circuit Breaker

Protect downstream services from webhook bursts:

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

4. Set Up Notification Channels

Get alerted when finance deliveries fail:

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": "Finance Pipeline Alerts",
    "type": "slack",
    "config": {
      "url": "https://hooks.slack.com/services/YOUR/ALERT/URL"
    },
    "events": ["delivery.failed", "destination.circuit_breaker.opened"],
    "filters": {
      "destinationIds": ["dst_finance01", "dst_finbackup01"]
    }
  }'

5. Currency Normalization

If you receive payments in multiple currencies, include original_amount_cents and original_currency fields in your unified schema (as shown in the transforms above). Your finance system can then apply live exchange rates for exact reconciliation. For approximate reporting, you can add a static rate lookup to the JSONata expression using $lookup() against a rates object like {"EUR": 1.08, "GBP": 1.27, "USD": 1}.

WARNING

Static exchange rates in JSONata expressions are suitable for approximate reporting only. For exact financial reconciliation, call a live exchange rate API in your finance system.

See Also

Released under the MIT License.