This guide shows you how to automatically create HubSpot contacts and deals from Instagram DM conversations. When someone completes a lead capture flow in InstantDM - providing their name, email, phone, or other details - that data flows directly into HubSpot as a new contact with all properties mapped.
You have three options, from no-code to full custom:
- Option A: Direct API integration (your own server)
- Option B: Via Make.com (no code)
- Option C: Via Zapier (no code)
Why Connect InstantDM to HubSpot?
Instagram is one of the highest-converting lead sources for many businesses, but manually copying lead data from DMs into your CRM is slow and error-prone. By connecting InstantDM to HubSpot, you:
- Capture leads automatically - every completed DM flow creates a HubSpot contact instantly
- Eliminate manual data entry - name, email, phone, and custom fields sync automatically
- Speed up follow-up - your sales team sees new leads in HubSpot within seconds
- Track lead source - know exactly which Instagram post and flow generated each lead
- Trigger HubSpot workflows - automatically enroll new Instagram leads in email sequences, assign to reps, or create deals
What Happens End-to-End
- Someone comments a keyword on your Instagram post (e.g., "pricing")
- InstantDM sends them a DM flow that collects their name, email, and interest
- When the flow completes, InstantDM fires a
flow_completedwebhook - The webhook data is routed to HubSpot, creating a new contact
- Optionally, a deal is created in your sales pipeline and your team is notified
What You'll Need
| Requirement | Details |
|---|---|
| InstantDM account | Trendsetter, Trendsetter Pro, or any Multi plan |
| InstantDM API key | Found at Settings → API (or /api-integration page) |
| HubSpot account | Free CRM works for contacts; Sales Hub for pipeline automation |
| Make.com account (Option B) | Free plan works for testing |
| Zapier account (Option C) | Free plan works for simple Zaps |
| Server (Option A) | Node.js or Python server for direct API integration |
For full API documentation, visit instantdm.com/instagram-api-docs.
Option A: Direct Integration (Webhook → Your Server → HubSpot API)
This option gives you the most control. Your server receives the InstantDM webhook, processes the data, and calls the HubSpot API directly.
Step 1: Set Up the Webhook Server
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
const INSTANTDM_API_KEY = process.env.INSTANTDM_API_KEY;
const HUBSPOT_ACCESS_TOKEN = process.env.HUBSPOT_ACCESS_TOKEN;
function verifySignature(req) {
const signature = req.headers['x-webhook-signature'];
if (!signature) return false;
const expected = crypto
.createHmac('sha256', INSTANTDM_API_KEY)
.update(JSON.stringify(req.body))
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
app.post('/webhook/instantdm', async (req, res) => {
res.status(200).json({ received: true });
if (!verifySignature(req)) {
console.error('Invalid webhook signature');
return;
}
const { event, data } = req.body;
if (event === 'flow_completed') {
await createHubSpotContact(data);
}
});
app.listen(3000, () => console.log('Server running on port 3000'));
Step 2: Create HubSpot Contacts via API
async function createHubSpotContact(data) {
const { username, response_variables, flow_name } = data;
const contactData = {
properties: {
email: response_variables.email,
firstname: response_variables.full_name?.split(' ')[0] || '',
lastname: response_variables.full_name?.split(' ').slice(1).join(' ') || '',
phone: response_variables.phone || '',
hs_lead_status: 'NEW',
leadsource: 'Instagram DM',
notes_last_contacted: `Completed "${flow_name}" flow on Instagram. Username: @${username}`
}
};
try {
const response = await fetch('https://api.hubapi.com/crm/v3/objects/contacts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${HUBSPOT_ACCESS_TOKEN}`
},
body: JSON.stringify(contactData)
});
if (response.status === 409) {
// Contact already exists - update instead
const existing = await response.json();
const contactId = existing.message.match(/ID: (\d+)/)?.[1];
if (contactId) {
await fetch(`https://api.hubapi.com/crm/v3/objects/contacts/${contactId}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${HUBSPOT_ACCESS_TOKEN}`
},
body: JSON.stringify(contactData)
});
console.log(`Updated existing HubSpot contact: ${contactId}`);
return contactId;
}
} else if (response.ok) {
const result = await response.json();
console.log(`Created HubSpot contact: ${result.id}`);
return result.id;
}
} catch (error) {
console.error('HubSpot API error:', error);
}
}
Step 3: Create Deals Automatically
async function createHubSpotDeal(contactId, data) {
const dealData = {
properties: {
dealname: `Instagram Lead - @${data.username}`,
pipeline: 'default',
dealstage: 'appointmentscheduled',
amount: data.response_variables.budget || '',
description: `Lead from Instagram DM flow: "${data.flow_name}". Interest: ${data.response_variables.interest || 'Not specified'}`
},
associations: [
{
to: { id: contactId },
types: [{ associationCategory: 'HUBSPOT_DEFINED', associationTypeId: 3 }]
}
]
};
const response = await fetch('https://api.hubapi.com/crm/v3/objects/deals', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${HUBSPOT_ACCESS_TOKEN}`
},
body: JSON.stringify(dealData)
});
if (response.ok) {
const result = await response.json();
console.log(`Created HubSpot deal: ${result.id}`);
}
}
Python Version (Direct API)
import os
import requests
import hmac
import hashlib
from flask import Flask, request, jsonify
import threading
app = Flask(__name__)
INSTANTDM_API_KEY = os.environ.get('INSTANTDM_API_KEY')
HUBSPOT_ACCESS_TOKEN = os.environ.get('HUBSPOT_ACCESS_TOKEN')
def verify_signature(req):
signature = req.headers.get('X-Webhook-Signature')
if not signature:
return False
expected = hmac.new(
INSTANTDM_API_KEY.encode(),
req.get_data(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
@app.route('/webhook/instantdm', methods=['POST'])
def webhook():
if not verify_signature(request):
return jsonify({'error': 'Invalid signature'}), 401
payload = request.get_json()
event = payload.get('event')
data = payload.get('data')
if event == 'flow_completed':
thread = threading.Thread(target=create_hubspot_contact, args=(data,))
thread.start()
return jsonify({'received': True}), 200
def create_hubspot_contact(data):
response_vars = data.get('response_variables', {})
full_name = response_vars.get('full_name', '')
name_parts = full_name.split(' ', 1)
contact_data = {
'properties': {
'email': response_vars.get('email', ''),
'firstname': name_parts[0] if name_parts else '',
'lastname': name_parts[1] if len(name_parts) > 1 else '',
'phone': response_vars.get('phone', ''),
'hs_lead_status': 'NEW',
'leadsource': 'Instagram DM',
'notes_last_contacted': f'Completed "{data.get("flow_name")}" flow. Username: @{data.get("username")}'
}
}
response = requests.post(
'https://api.hubapi.com/crm/v3/objects/contacts',
headers={
'Content-Type': 'application/json',
'Authorization': f'Bearer {HUBSPOT_ACCESS_TOKEN}'
},
json=contact_data
)
if response.status_code == 409:
# Contact exists - update
import re
match = re.search(r'ID: (\d+)', response.json().get('message', ''))
if match:
contact_id = match.group(1)
requests.patch(
f'https://api.hubapi.com/crm/v3/objects/contacts/{contact_id}',
headers={
'Content-Type': 'application/json',
'Authorization': f'Bearer {HUBSPOT_ACCESS_TOKEN}'
},
json=contact_data
)
elif response.ok:
result = response.json()
print(f'Created HubSpot contact: {result["id"]}')
if __name__ == '__main__':
app.run(port=3000)
Option B: Via Make.com (No Code)
This is the recommended approach if you don't want to write code. Make.com handles the webhook reception and HubSpot API calls with a visual interface.
Step 1: Create a Make.com Webhook
- Log in to make.com and click Create a new scenario.
- Add a Webhooks → Custom webhook module.
- Click Add, name it
InstantDM Leads, and click Save. - Copy the generated webhook URL.
Step 2: Configure InstantDM Webhook
- In InstantDM, go to Settings → API.
- Paste the Make.com webhook URL into the Webhook URL field.
- Enable the
flow_completedevent. - Click Save.
Step 3: Test the Connection
- In Make.com, click Run once on the webhook module.
- Trigger a test by completing a lead capture flow on your Instagram account (or use the Send Test Webhook button).
- Make.com receives the payload and maps the data structure.
Step 4: Add a Filter
- Click the connection line after the webhook module.
- Add a Filter:
eventequalsflow_completed.
Step 5: Add HubSpot - Create/Update Contact
- Add a new module: HubSpot → Create/Update a Contact.
- Connect your HubSpot account via OAuth.
- Map the fields:
| HubSpot Property | Make.com Field |
|---|---|
data.response_variables.email | |
| First Name | data.response_variables.full_name |
| Phone | data.response_variables.phone |
| Lead Source | Instagram DM (static text) |
| Notes | Completed "{{data.flow_name}}" flow. Interest: {{data.response_variables.interest}} |
Step 6: (Optional) Add HubSpot Deal
- Add another module: HubSpot → Create a Deal.
- Map:
| Deal Property | Value |
|---|---|
| Deal Name | Instagram Lead - @{{data.username}} |
| Pipeline | Select your sales pipeline |
| Deal Stage | Your first stage (e.g., New Lead) |
| Associated Contact | Contact ID from the previous step |
Step 7: Activate
- Test the full scenario end-to-end.
- Toggle the scenario to ON and set scheduling to Immediately.
Option C: Via Zapier (No Code)
Step 1: Create a Zapier Webhook Trigger
- Log in to zapier.com and click Create Zap.
- Trigger: Webhooks by Zapier → Catch Hook.
- Copy the generated webhook URL.
Step 2: Configure InstantDM
- In InstantDM, go to Settings → API.
- Paste the Zapier webhook URL and enable
flow_completed. - Click Save.
Step 3: Test the Trigger
- Click Test trigger in Zapier.
- Trigger a test event from InstantDM.
- Zapier detects the payload.
Step 4: Add Filter
- Add Filter by Zapier: Only continue if
eventexactly matchesflow_completed.
Step 5: Add HubSpot Action
- Add action: HubSpot → Create Contact.
- Connect your HubSpot account.
- Map the fields:
| HubSpot Field | Zapier Mapping |
|---|---|
data__response_variables__email | |
| First Name | data__response_variables__full_name |
| Phone | data__response_variables__phone |
| Lead Source | Instagram DM (static text) |
Step 6: Publish
- Test the Zap end-to-end.
- Click Publish to activate.
Tip: Zapier uses double-underscore notation for nested fields (e.g.,
data__response_variables__email).
Mapping InstantDM response_variables to HubSpot Contact Properties
The response_variables object in the flow_completed webhook contains all data collected during the DM flow. The field names depend on how you configured your question nodes in InstantDM's Flow Editor.
Common Field Mappings
| InstantDM Variable | HubSpot Property | HubSpot Internal Name |
|---|---|---|
full_name | First Name / Last Name | firstname / lastname |
email | email | |
phone | Phone Number | phone |
company | Company Name | company |
budget | Deal Amount | amount (on Deal object) |
interest | Notes | notes_last_contacted |
city | City | city |
website | Website URL | website |
Example Webhook Payload
{
"event": "flow_completed",
"timestamp": "2025-01-15T10:30:00Z",
"data": {
"instagram_user_id": "17841400123456789",
"username": "johndoe",
"flow_name": "Lead Capture Flow",
"response_variables": {
"full_name": "John Doe",
"email": "john@example.com",
"phone": "+1234567890",
"interest": "Premium Plan",
"company": "Acme Corp",
"budget": "5000"
}
}
}
Creating Custom HubSpot Properties
If you collect data that doesn't map to a default HubSpot property (e.g., instagram_username, preferred_service):
- Go to HubSpot → Settings → Properties.
- Click Create property.
- Name it (e.g.,
instagram_username). - Use the internal name in your API mapping or Make.com/Zapier field mapping.
Creating HubSpot Deals from Flow Completions
Deals let you track Instagram leads through your sales pipeline. Here's the recommended setup:
Deal Pipeline Structure
| Stage | Description |
|---|---|
| New Lead | Contact just created from Instagram flow |
| Contacted | Sales rep has reached out |
| Qualified | Lead confirmed as a good fit |
| Proposal Sent | Pricing or proposal delivered |
| Closed Won / Lost | Final outcome |
Automatic Deal Creation (API)
{
"properties": {
"dealname": "Instagram Lead - @johndoe",
"pipeline": "default",
"dealstage": "appointmentscheduled",
"amount": "5000",
"description": "Lead from Instagram DM flow: 'Lead Capture Flow'. Interest: Premium Plan"
},
"associations": [
{
"to": { "id": "CONTACT_ID" },
"types": [{ "associationCategory": "HUBSPOT_DEFINED", "associationTypeId": 3 }]
}
]
}
Adding Contacts to HubSpot Workflows
Once contacts are in HubSpot, you can enroll them in automated workflows:
Example Workflow: Instagram Lead Nurture
- Enrollment trigger: Contact property
leadsourceequalsInstagram DM - Step 1: Send welcome email immediately
- Step 2: Wait 2 days
- Step 3: Send case study email
- Step 4: Wait 3 days
- Step 5: Send pricing email with CTA to book a call
- Step 6: If no reply after 7 days, assign to sales rep for manual follow-up
Setting Up the Workflow
- Go to HubSpot → Automation → Workflows.
- Click Create workflow → Contact-based.
- Set enrollment trigger: Contact property
leadsourceisInstagram DM. - Add your email steps with delays.
- Activate the workflow.
Now every Instagram lead that enters HubSpot via InstantDM is automatically enrolled in your nurture sequence.
Example: Full Lead Capture Pipeline
Here's the complete end-to-end flow:
Instagram comment → DM flow → HubSpot contact → Email sequence
- Instagram Post: You publish a post about your service with CTA "Comment PRICING for details"
- InstantDM Trigger: Detects the keyword "PRICING" and starts the lead capture flow
- DM Flow: Asks for name, email, phone, and interest via conversational DM messages
- Webhook: InstantDM fires
flow_completedwith allresponse_variables - HubSpot Contact: Created automatically with all fields mapped
- HubSpot Deal: Created in your sales pipeline, associated with the contact
- HubSpot Workflow: Enrolls the contact in your Instagram Lead Nurture email sequence
- Slack Notification: Your sales team gets an alert in
#new-leads - Confirmation DM: InstantDM sends a thank-you message back to the user
Troubleshooting
| Issue | Solution |
|---|---|
| Contact not appearing in HubSpot | Check that the email field is mapped correctly - HubSpot requires a valid email to create a contact. |
| Duplicate contacts being created | Use HubSpot's "Create or Update" operation (Make.com/Zapier) or handle the 409 conflict response (direct API). |
| Missing fields in HubSpot | Verify the field names in response_variables match your flow's question node variable names exactly. |
| Make.com scenario not triggering | Ensure the scenario is set to ON and scheduling is Immediately. Check that the webhook URL is saved in InstantDM. |
| HubSpot API returning 401 | Regenerate your HubSpot access token or reconnect the OAuth in Make.com/Zapier. |
| Deal not associated with contact | Ensure you're passing the correct contactId from the Create Contact step to the Create Deal step. |
Frequently Asked Questions
Do I need HubSpot Sales Hub to connect InstantDM?
No. The free HubSpot CRM supports contact creation via API, Make.com, and Zapier. You only need Sales Hub if you want pipeline automation, sequences, or advanced deal management features.
Which method should I choose - direct API, Make.com, or Zapier?
Choose Make.com or Zapier if you want a no-code solution that's quick to set up. Choose direct API if you need custom logic (like conditional deal creation, data transformation, or integration with other internal systems). Make.com is generally more cost-effective than Zapier for high-volume use cases.
Can I update existing HubSpot contacts instead of creating duplicates?
Yes. All three methods support upsert behavior. HubSpot uses the email address as the unique identifier. Make.com's "Create/Update Contact" module and Zapier's "Create or Update Contact" action handle this automatically. For direct API, handle the 409 conflict response to update existing contacts.
How do I track which Instagram post generated the lead?
Include the post URL or post ID in your flow's response variables. In InstantDM's Flow Editor, you can pass metadata through the flow. Map this to a custom HubSpot property like instagram_source_post.
What if the user doesn't complete the DM flow?
The flow_completed webhook only fires when the user reaches the end of the flow. Partial completions don't trigger it. If you want to capture partial data, use the question_answered event instead and update the HubSpot contact incrementally as each question is answered.
Can I assign Instagram leads to specific sales reps automatically?
Yes. In HubSpot, set up a workflow that assigns contacts based on properties (e.g., by interest, location, or round-robin). Alternatively, set the hubspot_owner_id property in the API call or Make.com/Zapier mapping to assign directly during contact creation.
Can I send a confirmation DM after the HubSpot contact is created?
Yes. In Make.com, add an HTTP module after the HubSpot step that calls the InstantDM API (POST https://api.instantdm.com/api-webhook) to send a confirmation DM. In Zapier, add a "Webhooks by Zapier → POST" action. In the direct API approach, call your sendDM() function after the HubSpot contact is created.
What's Next
- Build a lead capture flow in InstantDM's Flow Editor with question nodes for name, email, and phone.
- Set up the integration using whichever method (A, B, or C) fits your needs.
- Create a HubSpot workflow to automatically nurture Instagram leads with email sequences.
- Read the Make.com integration guide for more Make.com scenario examples.
- Read the Zapier integration guide for more Zap examples.
- Get Slack notifications when new leads arrive.
- Explore the full API docs at instantdm.com/instagram-api-docs.