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
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
- Intercom sends conversation and contact webhooks to Hookbase
- HubSpot sends deal and contact webhooks to Hookbase
- Source-level signature verification validates each provider
- Routes apply filters by event type and value thresholds
- Transforms normalize both providers into a common customer schema
- Central customer DB receives a unified data stream
- High-value deal activity triggers Slack alerts
Step 1: Create Sources
Intercom Source
Create an Intercom source with webhook signature verification:
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:
{
"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:
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:
{
"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
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
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:
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"
}'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:
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"
}'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:
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"
}'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:
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"
}'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:
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"
}'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:
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:
{
"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:
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:
{
"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:
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:
{
"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:
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:
{
"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:
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):
[
{
"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):
[
{
"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:
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):
{
"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:
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:
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:
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:
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
}
}'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:
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:
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"
}
}
}'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:
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:
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
}
}'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.
Related Guides
- Transforms - JSONata syntax and advanced examples
- Filters - Complex routing logic and conditions
- Deduplication - Prevent duplicate event processing
- Failover - Ensure critical events are delivered
- Circuit Breaker - Protect downstream services
- Notification Channels - Configure delivery alerts
- Pipeline Guide - End-to-end webhook pipeline configuration
See Also
- HubSpot Integration — HubSpot webhook setup
- Intercom Integration — Intercom webhook setup
- Transforms — Normalize customer data
- SaaS User Onboarding — Related use case