Integrations / Reply handoff
First reply to HubSpot deal + Slack
When a contact replies to your LinkedIn outreach for the first time, create a deal in HubSpot and post the message to a Slack channel so your team can jump on the warm lead.
Why this matters
The first reply is the most valuable signal in outbound. Most teams don't learn about it until someone reads the inbox manually, by which point the lead has cooled. A Slack ping plus an auto-created deal closes the gap from minutes to seconds.
The trigger
- Event name:
contact.first_inbound_message - Fires: when an inbound message arrives from a contact who has never previously sent you one. The
is_firstfilter is implicit in the event itself; you can also setis_first: truedefensively. - Idempotency: the same delivery is retried up to 5 times on 5xx responses. Use
Webhook-Event-Idto dedupe before creating the deal.
Setup
Make.com handles the fan-out cleanly. n8n has a similar shape; replace the Make modules with HTTP Request nodes.
- In Make, create a scenario with a Webhooks → Custom webhook trigger. Copy the URL.
- Subscribe Crispy to
contact.first_inbound_message:curl -X POST https://crispy.sh/api/v1/subscriptions \ -H "Authorization: Bearer crispy_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "event": "contact.first_inbound_message", "target_url": "https://hook.us2.make.com/your-make-webhook-id", "filter": { "is_first": true } }' - Verify the signature using the HMAC verification snippet. In Make, use a Tools → Run JavaScript module before any side effects.
- Add a Router module with two parallel branches:
- Branch A: HubSpot → Create Deal. Map the contact email and any
custom_attrsyou want on the deal record. Usepayload.data.contact.external_ids.hubspot_contact_idif you imported it. - Branch B: Slack → Create a message. Post to
#replieswith the contact name and a snippet ofpayload.data.message.text.
- Branch A: HubSpot → Create Deal. Map the contact email and any
- Activate the scenario. New replies will fan out within seconds.
Sample payload
{
"payload_version": "1",
"id": "00000000-0000-0000-0000-000000000901",
"event": "contact.first_inbound_message",
"timestamp": "2026-04-29T14:12:33.000Z",
"webhook_id": "00000000-0000-0000-0000-0000000009h2",
"account_id": "00000000-0000-0000-0000-0000000009a1",
"data": {
"contact": {
"id": "00000000-0000-0000-0000-0000000009c2",
"workspace_id": "00000000-0000-0000-0000-0000000009w1",
"account_id": "00000000-0000-0000-0000-0000000009a1",
"connection_status": "connected",
"current_campaign_id": "00000000-0000-0000-0000-0000000009p1",
"tags": ["q4-target"],
"custom_attrs": {
"industry": "SaaS",
"title": "VP Engineering"
},
"external_ids": {
"hubspot_contact_id": "hs-12345"
}
},
"message": {
"id": "msg_abc123",
"text": "Thanks for reaching out — yes, I'd like to learn more.",
"received_at": "2026-04-29T14:12:30.000Z"
},
"source_event": "message.inbound_received",
"triggered_at": "2026-04-29T14:12:33.000Z"
}
}Troubleshooting
- I'm getting duplicate Slack messages.
- Crispy retries on 5xx. Add a dedup step keyed on
Webhook-Event-Idbefore creating the HubSpot deal or posting to Slack. - HubSpot is rejecting the contact email as already existing.
- Use HubSpot's “Upsert” pattern: search by email first, attach the deal to the existing contact if found, otherwise create-then-attach. Or pre-populate
external_ids.hubspot_contact_idon import so the payload already carries the link.