Skip to content

Cross-Platform Customer Data Sync

This use case demonstrates how to aggregate customer data from multiple CRM platforms—Intercom and HubSpot—into a single normalized schema, routing to a central customer database and sending Slack alerts for high-value deal activity.

Architecture

mermaid
flowchart LR
    Intercom[Intercom Webhooks] --> S1[Source: Intercom]
    HubSpot[HubSpot Webhooks] --> S2[Source: HubSpot]
    S1 --> R1[Route: Contacts]
    S1 --> R2[Route: Conversations]
    S2 --> R3[Route: Deals]
    S2 --> R4[Route: Contacts]
    R1 --> |Transform| D1[Customer DB API]
    R2 --> |Transform| D1
    R3 --> |Transform| D1
    R4 --> |Transform| D1
    R3 --> |Filter + Transform| D2[Slack #high-value]

Pattern: Bidirectional Fan-In

  1. Intercom sends conversation and contact webhooks to Hookbase
  2. HubSpot sends deal and contact webhooks to Hookbase
  3. Source-level signature verification validates each provider
  4. Routes apply filters by event type and value thresholds
  5. Transforms normalize both providers into a common customer schema
  6. Central customer DB receives a unified data stream
  7. High-value deal activity triggers Slack alerts

Step 1: Create Sources

Intercom Source

Create an Intercom source with webhook signature verification:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/sources \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Intercom Production",
    "slug": "intercom-prod",
    "description": "Production Intercom webhooks for contacts and conversations",
    "verificationConfig": {
      "type": "hmac",
      "secret": "YOUR_INTERCOM_WEBHOOK_SECRET",
      "algorithm": "sha256",
      "headerName": "X-Hub-Signature"
    }
  }'

Response:

json
{
  "data": {
    "id": "src_intercom01",
    "name": "Intercom Production",
    "slug": "intercom-prod",
    "url": "https://api.hookbase.app/ingest/your-org/intercom-prod",
    "verificationConfig": {
      "type": "hmac",
      "algorithm": "sha256"
    },
    "createdAt": "2026-03-07T10:00:00Z"
  }
}

TIP

Configure this URL in your Intercom Developer Hub under Webhooks → Your App → Webhook URLs.

HubSpot Source

Create a HubSpot source with v3 API signature verification:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/sources \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "HubSpot Production",
    "slug": "hubspot-prod",
    "description": "Production HubSpot webhooks for contacts and deals",
    "verificationConfig": {
      "type": "hmac",
      "secret": "YOUR_HUBSPOT_CLIENT_SECRET",
      "algorithm": "sha256",
      "headerName": "X-HubSpot-Signature-v3"
    }
  }'

Response:

json
{
  "data": {
    "id": "src_hubspot01",
    "name": "HubSpot Production",
    "slug": "hubspot-prod",
    "url": "https://api.hookbase.app/ingest/your-org/hubspot-prod",
    "verificationConfig": {
      "type": "hmac",
      "algorithm": "sha256"
    },
    "createdAt": "2026-03-07T10:01:00Z"
  }
}

TIP

Configure this URL in your HubSpot developer account under Apps → Your App → Webhooks. HubSpot sends batched payloads—each webhook request may contain multiple events in an array. See Step 4 for handling this.

Step 2: Create Destinations

Create two destinations for the customer database and Slack alerts:

Central Customer DB 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": "Customer DB API",
    "type": "http",
    "config": {
      "url": "https://api.yourcompany.com/customers/sync",
      "method": "POST",
      "headers": {
        "Authorization": "Bearer YOUR_CUSTOMER_DB_API_KEY",
        "Content-Type": "application/json",
        "X-Source": "hookbase"
      }
    },
    "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-deals",
    "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

Intercom Contacts Route

Route Intercom contact creation and updates to the customer DB:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/filters \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Intercom Contact Events",
    "conditions": [
      {
        "field": "topic",
        "operator": "in",
        "value": ["contact.created", "contact.updated", "contact.tag.created", "contact.tag.deleted"]
      }
    ],
    "logic": "AND"
  }'
bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/routes \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Intercom Contacts → Customer DB",
    "sourceId": "src_intercom01",
    "destinationId": "dst_customerdb01",
    "filterId": "flt_ic_contacts01",
    "transformId": "tfm_ic_contacts01",
    "enabled": true
  }'

Intercom Conversations Route

Route Intercom conversation events for customer interaction tracking:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/filters \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Intercom Conversation Events",
    "conditions": [
      {
        "field": "topic",
        "operator": "in",
        "value": ["conversation.created", "conversation.closed", "conversation.rated"]
      }
    ],
    "logic": "AND"
  }'
bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/routes \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Intercom Conversations → Customer DB",
    "sourceId": "src_intercom01",
    "destinationId": "dst_customerdb01",
    "filterId": "flt_ic_convos01",
    "transformId": "tfm_ic_convos01",
    "enabled": true
  }'

HubSpot Contacts Route

Route HubSpot contact events to the customer DB:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/filters \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "HubSpot Contact Events",
    "conditions": [
      {
        "field": "subscriptionType",
        "operator": "in",
        "value": ["contact.creation", "contact.propertyChange"]
      }
    ],
    "logic": "AND"
  }'
bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/routes \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "HubSpot Contacts → Customer DB",
    "sourceId": "src_hubspot01",
    "destinationId": "dst_customerdb01",
    "filterId": "flt_hs_contacts01",
    "transformId": "tfm_hs_contacts01",
    "enabled": true
  }'

HubSpot Deals Route

Route HubSpot deal events to the customer DB:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/filters \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "HubSpot Deal Events",
    "conditions": [
      {
        "field": "subscriptionType",
        "operator": "in",
        "value": ["deal.creation", "deal.propertyChange", "deal.deletion"]
      }
    ],
    "logic": "AND"
  }'
bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/routes \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "HubSpot Deals → Customer DB",
    "sourceId": "src_hubspot01",
    "destinationId": "dst_customerdb01",
    "filterId": "flt_hs_deals01",
    "transformId": "tfm_hs_deals01",
    "enabled": true
  }'

High-Value Deals → Slack Route

Alert on deals above $50,000 or deals entering closed-won stage:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/filters \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "High-Value Deal Activity",
    "conditions": [
      {
        "field": "subscriptionType",
        "operator": "in",
        "value": ["deal.creation", "deal.propertyChange"]
      },
      {
        "field": "propertyValue",
        "operator": "gte",
        "value": 50000
      }
    ],
    "logic": "OR"
  }'
bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/routes \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "High-Value Deals → Slack",
    "sourceId": "src_hubspot01",
    "destinationId": "dst_slack_hv01",
    "filterId": "flt_hv_deals01",
    "transformId": "tfm_slack_deals01",
    "enabled": true
  }'

TIP

The propertyValue threshold of 50000 represents $50,000 in deal value. Adjust based on your sales pipeline.

Step 4: Add Transforms

Intercom Contacts → Common Schema

Normalize Intercom contact data into a unified customer format:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/transforms \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Intercom Contact → Common Schema",
    "type": "jsonata",
    "config": {
      "expression": "{\n  \"action\": topic = \"contact.created\" ? \"create\" : \"update\",\n  \"source\": \"intercom\",\n  \"entity_type\": \"contact\",\n  \"external_ids\": {\n    \"intercom\": data.item.id,\n    \"email\": data.item.email\n  },\n  \"customer\": {\n    \"email\": data.item.email,\n    \"name\": data.item.name,\n    \"first_name\": $substringBefore(data.item.name, \" \"),\n    \"last_name\": $substringAfter(data.item.name, \" \"),\n    \"phone\": data.item.phone,\n    \"company\": data.item.companies.companies[0].name,\n    \"role\": data.item.role,\n    \"avatar_url\": data.item.avatar.image_url,\n    \"location\": {\n      \"city\": data.item.location_data.city_name,\n      \"region\": data.item.location_data.region_name,\n      \"country\": data.item.location_data.country_name\n    }\n  },\n  \"tags\": data.item.tags.tags.name,\n  \"metadata\": {\n    \"signed_up_at\": data.item.signed_up_at,\n    \"last_seen_at\": data.item.last_seen_at,\n    \"session_count\": data.item.session_count,\n    \"owner_id\": data.item.owner_id\n  },\n  \"timestamp\": $now()\n}"
    }
  }'

Example Output:

json
{
  "action": "create",
  "source": "intercom",
  "entity_type": "contact",
  "external_ids": {
    "intercom": "6489c3f28b1ea13a22b7c9a1",
    "email": "[email protected]"
  },
  "customer": {
    "email": "[email protected]",
    "name": "Jane Doe",
    "first_name": "Jane",
    "last_name": "Doe",
    "phone": "+1-555-0199",
    "company": "Acme Corp",
    "role": "user",
    "avatar_url": "https://static.intercomassets.com/avatars/123/square_128",
    "location": {
      "city": "San Francisco",
      "region": "California",
      "country": "United States"
    }
  },
  "tags": ["enterprise", "active-trial"],
  "metadata": {
    "signed_up_at": 1709280000,
    "last_seen_at": 1709366400,
    "session_count": 14,
    "owner_id": "5432112"
  },
  "timestamp": "2026-03-07T12:00:00Z"
}

Intercom Conversations → Common Schema

Normalize Intercom conversation events into customer interaction records:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/transforms \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Intercom Conversation → Common Schema",
    "type": "jsonata",
    "config": {
      "expression": "{\n  \"action\": \"interaction\",\n  \"source\": \"intercom\",\n  \"entity_type\": \"conversation\",\n  \"external_ids\": {\n    \"intercom_conversation\": data.item.id,\n    \"intercom_contact\": data.item.contacts.contacts[0].id,\n    \"email\": data.item.contacts.contacts[0].email\n  },\n  \"interaction\": {\n    \"type\": topic = \"conversation.created\" ? \"opened\" : (topic = \"conversation.closed\" ? \"closed\" : \"rated\"),\n    \"subject\": data.item.source.subject,\n    \"channel\": data.item.source.type,\n    \"assignee_id\": data.item.assignee.id,\n    \"assignee_name\": data.item.assignee.name,\n    \"rating\": data.item.conversation_rating.rating,\n    \"rating_remark\": data.item.conversation_rating.remark,\n    \"tags\": data.item.tags.tags.name,\n    \"duration_seconds\": topic = \"conversation.closed\" ? ($toMillis(data.item.statistics.last_close_at) - $toMillis(data.item.statistics.first_contact_reply_at)) / 1000 : null\n  },\n  \"metadata\": {\n    \"total_count\": data.item.statistics.count_reopens + 1,\n    \"first_response_seconds\": data.item.statistics.first_admin_reply_at\n  },\n  \"timestamp\": $now()\n}"
    }
  }'

Example Output:

json
{
  "action": "interaction",
  "source": "intercom",
  "entity_type": "conversation",
  "external_ids": {
    "intercom_conversation": "403920101",
    "intercom_contact": "6489c3f28b1ea13a22b7c9a1",
    "email": "[email protected]"
  },
  "interaction": {
    "type": "closed",
    "subject": "Issue with billing integration",
    "channel": "email",
    "assignee_id": "9876543",
    "assignee_name": "Support Team",
    "rating": 5,
    "rating_remark": "Fast resolution, thank you!",
    "tags": ["billing", "integration"],
    "duration_seconds": 3420
  },
  "metadata": {
    "total_count": 1,
    "first_response_seconds": 1709282400
  },
  "timestamp": "2026-03-07T12:05:00Z"
}

HubSpot Contacts → Common Schema

Normalize HubSpot contact data into the same unified format:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/transforms \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "HubSpot Contact → Common Schema",
    "type": "jsonata",
    "config": {
      "expression": "{\n  \"action\": subscriptionType = \"contact.creation\" ? \"create\" : \"update\",\n  \"source\": \"hubspot\",\n  \"entity_type\": \"contact\",\n  \"external_ids\": {\n    \"hubspot\": $string(objectId),\n    \"email\": properties.email.value\n  },\n  \"customer\": {\n    \"email\": properties.email.value,\n    \"name\": properties.firstname.value & \" \" & properties.lastname.value,\n    \"first_name\": properties.firstname.value,\n    \"last_name\": properties.lastname.value,\n    \"phone\": properties.phone.value,\n    \"company\": properties.company.value,\n    \"role\": properties.jobtitle.value,\n    \"avatar_url\": null,\n    \"location\": {\n      \"city\": properties.city.value,\n      \"region\": properties.state.value,\n      \"country\": properties.country.value\n    }\n  },\n  \"tags\": [],\n  \"metadata\": {\n    \"lifecycle_stage\": properties.lifecyclestage.value,\n    \"lead_status\": properties.hs_lead_status.value,\n    \"hubspot_owner_id\": properties.hubspot_owner_id.value,\n    \"last_modified\": properties.lastmodifieddate.value\n  },\n  \"timestamp\": $now()\n}"
    }
  }'

Example Output:

json
{
  "action": "create",
  "source": "hubspot",
  "entity_type": "contact",
  "external_ids": {
    "hubspot": "1029384",
    "email": "[email protected]"
  },
  "customer": {
    "email": "[email protected]",
    "name": "John Smith",
    "first_name": "John",
    "last_name": "Smith",
    "phone": "+1-555-0234",
    "company": "Globex Corporation",
    "role": "VP Engineering",
    "avatar_url": null,
    "location": {
      "city": "Austin",
      "region": "Texas",
      "country": "United States"
    }
  },
  "tags": [],
  "metadata": {
    "lifecycle_stage": "opportunity",
    "lead_status": "OPEN",
    "hubspot_owner_id": "87654321",
    "last_modified": "2026-03-07T11:30:00Z"
  },
  "timestamp": "2026-03-07T12:10:00Z"
}

HubSpot Deals → Common Schema

Normalize HubSpot deal events into customer deal records:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/transforms \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "HubSpot Deal → Common Schema",
    "type": "jsonata",
    "config": {
      "expression": "{\n  \"action\": subscriptionType = \"deal.creation\" ? \"create\" : (subscriptionType = \"deal.deletion\" ? \"delete\" : \"update\"),\n  \"source\": \"hubspot\",\n  \"entity_type\": \"deal\",\n  \"external_ids\": {\n    \"hubspot_deal\": $string(objectId),\n    \"hubspot_contact\": $string(properties.associatedcontactid.value)\n  },\n  \"deal\": {\n    \"name\": properties.dealname.value,\n    \"stage\": properties.dealstage.value,\n    \"amount\": $number(properties.amount.value),\n    \"currency\": properties.deal_currency_code.value,\n    \"pipeline\": properties.pipeline.value,\n    \"close_date\": properties.closedate.value,\n    \"owner_id\": properties.hubspot_owner_id.value,\n    \"changed_property\": propertyName,\n    \"previous_value\": propertyValue\n  },\n  \"metadata\": {\n    \"portal_id\": portalId,\n    \"change_source\": changeSource,\n    \"last_modified\": properties.lastmodifieddate.value\n  },\n  \"timestamp\": $now()\n}"
    }
  }'

Example Output:

json
{
  "action": "update",
  "source": "hubspot",
  "entity_type": "deal",
  "external_ids": {
    "hubspot_deal": "5678901",
    "hubspot_contact": "1029384"
  },
  "deal": {
    "name": "Globex Enterprise License",
    "stage": "closedwon",
    "amount": 75000,
    "currency": "USD",
    "pipeline": "default",
    "close_date": "2026-03-15T00:00:00Z",
    "owner_id": "87654321",
    "changed_property": "dealstage",
    "previous_value": "contractsent"
  },
  "metadata": {
    "portal_id": 12345678,
    "change_source": "CRM_UI",
    "last_modified": "2026-03-07T11:45:00Z"
  },
  "timestamp": "2026-03-07T12:15:00Z"
}

HubSpot Batch Payload Transform

HubSpot sends webhook payloads as arrays—each request may contain multiple events batched together. Use a pre-processing transform to fan out batched events into individual records:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/transforms \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "HubSpot Batch → Individual Events",
    "type": "jsonata",
    "config": {
      "expression": "$map($, function($event) {\n  {\n    \"subscriptionType\": $event.subscriptionType,\n    \"objectId\": $event.objectId,\n    \"propertyName\": $event.propertyName,\n    \"propertyValue\": $event.propertyValue,\n    \"changeSource\": $event.changeSource,\n    \"portalId\": $event.portalId,\n    \"appId\": $event.appId,\n    \"occurredAt\": $event.occurredAt,\n    \"eventId\": $event.eventId\n  }\n})"
    }
  }'

WARNING

HubSpot batches multiple subscription events into a single POST request. If you receive an array payload like [{event1}, {event2}, {event3}], this transform ensures each event is processed individually through your routes. Apply this transform at the source level before event-specific transforms run.

Example Input (batched from HubSpot):

json
[
  {
    "subscriptionType": "contact.creation",
    "objectId": 1029384,
    "portalId": 12345678,
    "occurredAt": 1709712000000,
    "eventId": 100
  },
  {
    "subscriptionType": "deal.propertyChange",
    "objectId": 5678901,
    "propertyName": "dealstage",
    "propertyValue": "closedwon",
    "portalId": 12345678,
    "occurredAt": 1709712001000,
    "eventId": 101
  }
]

Example Output (individual events):

json
[
  {
    "subscriptionType": "contact.creation",
    "objectId": 1029384,
    "portalId": 12345678,
    "occurredAt": 1709712000000,
    "eventId": 100
  },
  {
    "subscriptionType": "deal.propertyChange",
    "objectId": 5678901,
    "propertyName": "dealstage",
    "propertyValue": "closedwon",
    "portalId": 12345678,
    "occurredAt": 1709712001000,
    "eventId": 101
  }
]

Slack Alert for High-Value Deals

Format high-value deal activity as a rich Slack Block Kit message:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/transforms \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "HubSpot Deal → Slack Alert",
    "type": "jsonata",
    "config": {
      "expression": "(\n  $amount := $number(properties.amount.value);\n  $stage := properties.dealstage.value;\n  $isWon := $stage = \"closedwon\";\n  $isNew := subscriptionType = \"deal.creation\";\n  $color := $isWon ? \"#00ff00\" : ($amount >= 100000 ? \"#ff9900\" : \"#0066ff\");\n  $emoji := $isWon ? \":trophy:\" : ($isNew ? \":rocket:\" : \":chart_with_upwards_trend:\");\n  $action := $isWon ? \"Closed Won\" : ($isNew ? \"New Deal Created\" : \"Deal Updated\");\n  {\n    \"attachments\": [\n      {\n        \"color\": $color,\n        \"blocks\": [\n          {\n            \"type\": \"header\",\n            \"text\": {\n              \"type\": \"plain_text\",\n              \"text\": $emoji & \" High-Value Deal: \" & $action\n            }\n          },\n          {\n            \"type\": \"section\",\n            \"fields\": [\n              {\n                \"type\": \"mrkdwn\",\n                \"text\": \"*Deal:*\\n\" & properties.dealname.value\n              },\n              {\n                \"type\": \"mrkdwn\",\n                \"text\": \"*Amount:*\\n$\" & $formatNumber($amount, \"#,##0\")\n              },\n              {\n                \"type\": \"mrkdwn\",\n                \"text\": \"*Stage:*\\n\" & $stage\n              },\n              {\n                \"type\": \"mrkdwn\",\n                \"text\": \"*Pipeline:*\\n\" & properties.pipeline.value\n              }\n            ]\n          },\n          {\n            \"type\": \"section\",\n            \"fields\": [\n              {\n                \"type\": \"mrkdwn\",\n                \"text\": \"*Close Date:*\\n\" & properties.closedate.value\n              },\n              {\n                \"type\": \"mrkdwn\",\n                \"text\": \"*Changed Property:*\\n\" & (propertyName ? propertyName : \"N/A\")\n              }\n            ]\n          },\n          {\n            \"type\": \"context\",\n            \"elements\": [\n              {\n                \"type\": \"mrkdwn\",\n                \"text\": \"Deal ID: \" & $string(objectId) & \" | Portal: \" & $string(portalId) & \" | <https://app.hubspot.com/contacts/\" & $string(portalId) & \"/deal/\" & $string(objectId) & \"|View in HubSpot>\"\n              }\n            ]\n          }\n        ]\n      }\n    ]\n  }\n)"
    }
  }'

Example Output (Slack Message):

json
{
  "attachments": [
    {
      "color": "#00ff00",
      "blocks": [
        {
          "type": "header",
          "text": {
            "type": "plain_text",
            "text": ":trophy: High-Value Deal: Closed Won"
          }
        },
        {
          "type": "section",
          "fields": [
            {
              "type": "mrkdwn",
              "text": "*Deal:*\nGlobex Enterprise License"
            },
            {
              "type": "mrkdwn",
              "text": "*Amount:*\n$75,000"
            },
            {
              "type": "mrkdwn",
              "text": "*Stage:*\nclosedwon"
            },
            {
              "type": "mrkdwn",
              "text": "*Pipeline:*\ndefault"
            }
          ]
        },
        {
          "type": "section",
          "fields": [
            {
              "type": "mrkdwn",
              "text": "*Close Date:*\n2026-03-15T00:00:00Z"
            },
            {
              "type": "mrkdwn",
              "text": "*Changed Property:*\ndealstage"
            }
          ]
        },
        {
          "type": "context",
          "elements": [
            {
              "type": "mrkdwn",
              "text": "Deal ID: 5678901 | Portal: 12345678 | <https://app.hubspot.com/contacts/12345678/deal/5678901|View in HubSpot>"
            }
          ]
        }
      ]
    }
  ]
}

Step 5: Test the Pipeline

Test Intercom Contact Webhook

Send a test Intercom-like payload to verify contact normalization:

bash
curl -X POST https://api.hookbase.app/ingest/your-org/intercom-prod \
  -H "Content-Type: application/json" \
  -H "X-Hub-Signature: sha256=test_signature" \
  -d '{
    "topic": "contact.created",
    "data": {
      "item": {
        "id": "6489c3f28b1ea13a22b7c9a1",
        "email": "[email protected]",
        "name": "Jane Doe",
        "phone": "+1-555-0199",
        "role": "user",
        "avatar": {
          "image_url": "https://static.intercomassets.com/avatars/123/square_128"
        },
        "companies": {
          "companies": [
            { "name": "Acme Corp" }
          ]
        },
        "location_data": {
          "city_name": "San Francisco",
          "region_name": "California",
          "country_name": "United States"
        },
        "tags": {
          "tags": [
            { "name": "enterprise" },
            { "name": "active-trial" }
          ]
        },
        "signed_up_at": 1709280000,
        "last_seen_at": 1709366400,
        "session_count": 14,
        "owner_id": "5432112"
      }
    }
  }'

Test HubSpot Deal Webhook

Send a test HubSpot-like batched payload to verify deal processing:

bash
curl -X POST https://api.hookbase.app/ingest/your-org/hubspot-prod \
  -H "Content-Type: application/json" \
  -H "X-HubSpot-Signature-v3: test_signature" \
  -d '[
    {
      "subscriptionType": "deal.propertyChange",
      "objectId": 5678901,
      "propertyName": "dealstage",
      "propertyValue": "closedwon",
      "changeSource": "CRM_UI",
      "portalId": 12345678,
      "appId": 98765,
      "occurredAt": 1709712000000,
      "eventId": 101,
      "properties": {
        "dealname": { "value": "Globex Enterprise License" },
        "dealstage": { "value": "closedwon" },
        "amount": { "value": "75000" },
        "deal_currency_code": { "value": "USD" },
        "pipeline": { "value": "default" },
        "closedate": { "value": "2026-03-15T00:00:00Z" },
        "hubspot_owner_id": { "value": "87654321" },
        "associatedcontactid": { "value": "1029384" },
        "lastmodifieddate": { "value": "2026-03-07T11:45:00Z" }
      }
    }
  ]'

TIP

For production testing, use the HubSpot webhook testing tool in your app's developer portal or create test contacts and deals through the HubSpot API.

Verify Deliveries:

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

You should see deliveries for each test:

  • Intercom contact test: 1 delivery to Customer DB API with normalized contact schema
  • HubSpot deal test: 1 delivery to Customer DB API with normalized deal schema + 1 delivery to Slack (if deal value >= $50,000)

Production Considerations

1. Enable Deduplication

Prevent duplicate processing when Intercom or HubSpot retries a failed delivery:

bash
curl -X PATCH https://api.hookbase.app/api/organizations/{orgId}/sources/src_intercom01 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "deduplicationConfig": {
      "enabled": true,
      "keyPath": "data.item.id",
      "windowSeconds": 86400
    }
  }'
bash
curl -X PATCH https://api.hookbase.app/api/organizations/{orgId}/sources/src_hubspot01 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "deduplicationConfig": {
      "enabled": true,
      "keyPath": "[0].eventId",
      "windowSeconds": 86400
    }
  }'

TIP

HubSpot's eventId field is unique per webhook event, making it ideal for deduplication. For Intercom, use the nested item ID to catch retries of the same event.

2. Configure Circuit Breakers

Protect the customer DB from overload during bulk imports or CRM migrations:

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

3. Set Up Failover for Customer DB

Ensure customer data reaches a backup endpoint if the primary is unavailable:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/destinations \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Customer DB API Backup",
    "type": "http",
    "config": {
      "url": "https://api-backup.yourcompany.com/customers/sync",
      "method": "POST",
      "headers": {
        "Authorization": "Bearer YOUR_BACKUP_API_KEY",
        "Content-Type": "application/json",
        "X-Source": "hookbase-failover"
      }
    }
  }'
bash
curl -X PATCH https://api.hookbase.app/api/organizations/{orgId}/routes/rte_ic_contacts01 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "failoverConfig": {
      "enabled": true,
      "destinationId": "dst_backup01",
      "triggerAfterAttempts": 3
    }
  }'

4. Set Up Notification Channels

Get alerted when customer sync 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": "Customer Sync Alerts",
    "type": "slack",
    "config": {
      "url": "https://hooks.slack.com/services/YOUR/ALERT/URL"
    },
    "events": ["delivery.failed", "destination.circuit_breaker.opened"],
    "filters": {
      "destinationIds": ["dst_customerdb01"]
    }
  }'

5. Bidirectional Enrichment

To enrich data flowing between Intercom and HubSpot, create additional routes that send normalized records back to each platform. For example, when a HubSpot deal closes, update the corresponding Intercom contact with deal information:

bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/destinations \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Intercom Contact Update API",
    "type": "http",
    "config": {
      "url": "https://api.intercom.io/contacts",
      "method": "PUT",
      "headers": {
        "Authorization": "Bearer YOUR_INTERCOM_ACCESS_TOKEN",
        "Content-Type": "application/json",
        "Accept": "application/json"
      }
    },
    "retryConfig": {
      "maxAttempts": 3,
      "backoffMultiplier": 2,
      "initialInterval": 2000
    }
  }'
bash
curl -X POST https://api.hookbase.app/api/organizations/{orgId}/transforms \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "HubSpot Deal Won → Intercom Tag",
    "type": "jsonata",
    "config": {
      "expression": "properties.dealstage.value = \"closedwon\" ? {\n  \"id\": $string(properties.associatedcontactid.value),\n  \"custom_attributes\": {\n    \"hubspot_deal_value\": $number(properties.amount.value),\n    \"hubspot_deal_stage\": properties.dealstage.value,\n    \"hubspot_deal_name\": properties.dealname.value,\n    \"last_synced_at\": $now()\n  }\n} : null"
    }
  }'

WARNING

When implementing bidirectional sync, be careful to avoid infinite loops. Use the changeSource field from HubSpot and source headers from Intercom to filter out changes that originated from your own sync process.

See Also

Released under the MIT License.