HubSpot Webhooks: CRM Integration Patterns
Learn how to build reliable HubSpot webhook integrations for your CRM workflows. Complete guide covering event subscriptions, signature verification, batch processing, and real-world use cases for lead sync and sales automation.

HubSpot Webhooks: CRM Integration Patterns
HubSpot webhooks enable real-time data sync between CRM and external systems for lead routing, contact warehousing, and marketing automation. This guide covers setup through advanced batch processing.
Understanding HubSpot Webhook Subscriptions
HubSpot uses a subscription-based model tied to your app. Webhooks are created via dashboard or API.
Event Types
Contact Events
contact.creation- New contact added to CRMcontact.deletion- Contact removedcontact.propertyChange- Any contact property updatedcontact.privacyDeletion- GDPR deletion request
Company Events
company.creation- New company recordcompany.deletion- Company removedcompany.propertyChange- Company property updated
Deal Events
deal.creation- New deal createddeal.deletion- Deal removeddeal.propertyChange- Deal property changed (stages, amounts, etc.)
Conversation Events
conversation.creation- New conversation startedconversation.newMessage- Message added to conversation
Setting Up Webhook Subscriptions
Via the HubSpot Developer Portal
- Navigate to your app in the HubSpot Developer Portal
- Select "Webhooks" from the left sidebar
- Configure your target URL endpoint
- Select the subscription types you need
- Save and activate the subscriptions
Programmatic Setup via API
For more control, create subscriptions programmatically:
const axios = require('axios');
async function createWebhookSubscription(appId, accessToken) {
const subscriptions = [
{
subscriptionType: 'contact.creation',
propertyName: null,
active: true
},
{
subscriptionType: 'contact.propertyChange',
propertyName: 'email',
active: true
},
{
subscriptionType: 'deal.propertyChange',
propertyName: 'dealstage',
active: true
}
];
for (const subscription of subscriptions) {
await axios.post(
`https://api.hubapi.com/webhooks/v3/${appId}/subscriptions`,
subscription,
{
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
}
);
}
}propertyChange events require specifying which property to monitor—this keeps volume manageable.
Handling HubSpot Webhook Events
HubSpot sends batched arrays, even for single events. Your endpoint must handle batch format. See receiving and verifying webhooks in Node.js.
Basic Event Handler
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
const CLIENT_SECRET = process.env.HUBSPOT_CLIENT_SECRET;
app.post('/webhooks/hubspot', async (req, res) => {
// HubSpot expects a response within 5 seconds
res.status(200).send('OK');
// Process events asynchronously
processHubSpotEvents(req.body).catch(err => {
console.error('Event processing failed:', err);
});
});
async function processHubSpotEvents(events) {
for (const event of events) {
switch (event.subscriptionType) {
case 'contact.creation':
await handleNewContact(event);
break;
case 'contact.propertyChange':
await handleContactUpdate(event);
break;
case 'deal.propertyChange':
await handleDealChange(event);
break;
default:
console.log('Unhandled event type:', event.subscriptionType);
}
}
}Signature Verification
Always verify HubSpot signatures in production. This is critical webhook security best practice.
function verifyHubSpotSignature(req) {
const signature = req.headers['x-hubspot-signature-v3'];
const timestamp = req.headers['x-hubspot-request-timestamp'];
// Reject requests older than 5 minutes
const currentTime = Date.now();
if (currentTime - parseInt(timestamp) > 300000) {
throw new Error('Request timestamp too old');
}
// Build the signature base string
const requestBody = JSON.stringify(req.body);
const uri = `https://${req.headers.host}${req.originalUrl}`;
const signatureBaseString = `${req.method}${uri}${requestBody}${timestamp}`;
// Calculate expected signature
const expectedSignature = crypto
.createHmac('sha256', CLIENT_SECRET)
.update(signatureBaseString)
.digest('base64');
if (signature !== expectedSignature) {
throw new Error('Invalid signature');
}
return true;
}
// Middleware for signature verification
function hubspotSignatureMiddleware(req, res, next) {
try {
verifyHubSpotSignature(req);
next();
} catch (error) {
console.error('Signature verification failed:', error.message);
res.status(401).send('Unauthorized');
}
}
app.post('/webhooks/hubspot', hubspotSignatureMiddleware, async (req, res) => {
// Handler code here
});Real-World Use Cases
Lead Sync to External Systems
Auto-sync new contacts to billing systems, support desks, or custom apps:
async function handleNewContact(event) {
const contactId = event.objectId;
// Fetch full contact details from HubSpot API
const contact = await hubspotClient.crm.contacts.basicApi.getById(
contactId,
['email', 'firstname', 'lastname', 'company', 'phone']
);
// Sync to your external system
await externalCRM.createLead({
email: contact.properties.email,
name: `${contact.properties.firstname} ${contact.properties.lastname}`,
company: contact.properties.company,
source: 'hubspot',
hubspotId: contactId
});
}Deal Stage Change Notifications
Trigger actions when deals move through pipeline:
async function handleDealChange(event) {
if (event.propertyName !== 'dealstage') return;
const dealId = event.objectId;
const newStage = event.propertyValue;
// Fetch deal details
const deal = await hubspotClient.crm.deals.basicApi.getById(
dealId,
['dealname', 'amount', 'hubspot_owner_id'],
undefined,
['contacts']
);
// Stage-specific actions
switch (newStage) {
case 'qualifiedtobuy':
await notifySalesTeam(deal);
break;
case 'closedwon':
await triggerOnboarding(deal);
await updateRevenueMetrics(deal);
break;
case 'closedlost':
await logLostDealAnalytics(deal);
break;
}
}Marketing Automation Triggers
Trigger marketing workflows on contact property changes:
async function handleContactUpdate(event) {
const contactId = event.objectId;
const propertyName = event.propertyName;
const newValue = event.propertyValue;
// Trigger campaigns based on lifecycle stage changes
if (propertyName === 'lifecyclestage' && newValue === 'customer') {
await marketingPlatform.enrollInCampaign(contactId, 'customer-onboarding');
}
// Update email preferences in external ESP
if (propertyName === 'email') {
await emailServiceProvider.updateSubscriber({
oldEmail: event.propertyValueBefore,
newEmail: newValue,
hubspotId: contactId
});
}
}Batch Processing Best Practices
HubSpot batches events for efficiency. A single request may have dozens of events, especially during bulk imports or workflows.
async function processHubSpotEvents(events) {
// Group events by type for batch processing
const eventsByType = events.reduce((acc, event) => {
const type = event.subscriptionType;
if (!acc[type]) acc[type] = [];
acc[type].push(event);
return acc;
}, {});
// Process each type in parallel
const processors = Object.entries(eventsByType).map(([type, typeEvents]) => {
return processBatchByType(type, typeEvents);
});
await Promise.all(processors);
}
async function processBatchByType(type, events) {
// Fetch all records in one batch API call
const records = await hubspotClient.crm.contacts.batchApi.read({
inputs: events.map(e => ({ id: e.objectId })),
properties: ['email', 'firstname', 'lastname']
});
for (const record of records.results) {
await processRecord(type, record);
}
}Deduplication
HubSpot may send duplicates. Use idempotent handlers with event IDs:
const processedEvents = new Set(); // Use Redis in production
async function processEventWithDeduplication(event) {
const eventKey = `${event.eventId}-${event.subscriptionType}`;
if (processedEvents.has(eventKey)) {
console.log('Skipping duplicate event:', eventKey);
return;
}
processedEvents.add(eventKey);
await processEvent(event);
}Production Best Practices
Respond immediately: HubSpot expects 200 within 5 seconds.
Process asynchronously: Queue for background processing to avoid timeout issues.
Handle partial failures: Log failures and continue batch processing.
Monitor health: Track success rates, processing times, errors. HubSpot disables failing subscriptions.
Implement retries: Retry failed events with exponential backoff.
Simplifying CRM Webhooks with Hook Mesh
Hook Mesh provides managed CRM webhook delivery with automatic signature verification, intelligent retry policies, and event queuing for batched delivery. Let your team focus on business logic instead of infrastructure.
Conclusion
HubSpot webhooks enable real-time CRM workflows. Success requires understanding subscriptions, handling batched events, and verifying signatures.
Start with core event types, implement async processing from day one, and consider managed infrastructure as complexity grows.
See SendGrid email webhooks for delivery tracking or Slack webhooks for team notifications. Browse our Platform Integration Hub.
Related Posts
SendGrid Webhook Events: How to Handle Email Notifications
Learn how to set up and handle SendGrid Event Webhooks for email delivery tracking. Complete guide covering event types, payload structure, signature verification, and best practices with Node.js code examples.
Slack Webhooks: Incoming, Outgoing, and Event Subscriptions
A comprehensive guide to Slack webhooks covering incoming webhooks, outgoing webhooks, and the Events API. Learn how to integrate Slack with your applications using Node.js code examples and best practices.
Webhook Security Best Practices: The Complete Guide
Learn how to secure your webhook implementations with HMAC signature verification, replay attack prevention, SSRF mitigation, and more. Includes code examples in Node.js and Python.
Webhook Idempotency: Why It Matters and How to Implement It
A comprehensive technical guide to implementing idempotency for webhooks. Learn about idempotency keys, deduplication strategies, and implementation patterns with Node.js and Python code examples.
Webhook Integration Guides: Connect with Any Platform
Comprehensive guides for integrating webhooks from Stripe, GitHub, Shopify, Twilio, and 10+ other platforms. Learn best practices for reliable webhook handling across your entire stack.