Overview
This app keeps Instantly outreach activity on the matching HubSpot contact so sales and ops can see sends, replies, and opt-outs in one place. Instantly (and HubSpot, for exclusions) POST events to this app. The app looks up the contact by email, writes timeline activity, and updates lead status when the event calls for it.
Email activity
Sent mail and replies land on the HubSpot timeline.
Lead status
Replies, not-interested, and opt-outs update prospecting status.
Compliance
Opt-outs and HubSpot exclusion lists stop further Instantly outreach.
Contacts must already exist in HubSpot. If there is no match, the event is skipped and recorded as a “contact not found” — the app does not create HubSpot contacts.
How it works
Step 1
Event fires
Instantly or a HubSpot workflow sends a webhook to this app.
Step 2
Request is verified
Signatures or the shared webhook secret are checked before any work runs.
Step 3
HubSpot is updated
The contact is found by email, then engagements and properties are written.
Step 4
Failures retry
Failed events go on a retry queue. Persistent failures show on the dashboard.
What gets synced
| Trigger | HubSpot result | prospecting_status |
|---|---|---|
| Email sent | Sent email engagement; last Instantly contact date | Unchanged |
| Reply received (interested) | Incoming email engagement | Warm Lead |
| Reply received (not interested) | Incoming email engagement | Cold Lead |
| Lead not interested | Timeline note | Cold Lead |
| Opt-out | Note; optional HubSpot static list add | Opted out of Email/SMS Outreach |
| HubSpot exclusion list | No HubSpot write — email is added to Instantly exclusions | Unchanged |
Required HubSpot properties: prospecting_status and last_instantlyai_contact_date.
Dashboard
The monitoring dashboard is at /dashboard. Unlock it with the DASHBOARD_API_KEY environment variable. The key is stored in session storage for this browser tab only.
What to watch
- Success rate should stay high (typically above 98%).
- A growing retry queue usually means HubSpot or Instantly is failing or rate-limiting.
- Contacts not found means Instantly emailed someone who is not in HubSpot yet.
- Recent Errors lists the last 50 webhook and retry failures, with correlation IDs.
Metric cards
- Last 24 hours / last hour — inbound webhook volume.
- Success rate and average processing time — how cleanly events complete.
- Active retries — items still waiting to be replayed.
- Audit events — security and skip events written to the audit log.
Webhooks
Point Instantly (Settings → Webhooks) and the HubSpot exclusion workflow at these URLs. Instantly’s webhook secret must match WEBHOOK_SECRET.
Email Sent
Instantly
https://<your-app>/api/webhooks/instantly/email-sentLogs a sent-email engagement on the HubSpot contact and updates last Instantly contact date.
HMAC signature (x-instantly-signature or x-signature)
Reply Received
Instantly
https://<your-app>/api/webhooks/instantly/reply-receivedLogs the inbound reply. Sets Warm Lead when sentiment is interested; Cold Lead when not interested.
HMAC signature (x-instantly-signature or x-signature)
Opt-Out
Instantly
https://<your-app>/api/webhooks/instantly/opt-outWrites an opt-out note, sets prospecting status to Opted out of Email/SMS Outreach, and adds the contact to the HubSpot blocklist when configured.
HMAC signature (x-instantly-signature or x-signature)
Lead Not Interested
Instantly
https://<your-app>/api/webhooks/instantly/lead-not-interestedWrites a note and sets prospecting status to Cold Lead.
HMAC signature (x-instantly-signature or x-signature)
Lead Status Changed
Instantly
https://<your-app>/api/webhooks/instantly/lead-status-changedWrites a note and sets prospecting status to Cold Lead.
HMAC signature (x-instantly-signature or x-signature)
Exclusion List
HubSpot
https://<your-app>/api/webhooks/hubspot/exclusion-listAdds the contact email to Instantly’s global exclusion list so they are not emailed again.
HubSpot signature, or WEBHOOK_SECRET request header
Duplicate deliveries are ignored. Email addresses are encrypted at rest and masked in logs.
Operations
| Job | Schedule | Purpose |
|---|---|---|
| GET /api/cron/retry | Every 5 minutes | Replays failed events (up to 3 attempts, exponential backoff capped at 8 minutes). |
| GET /api/cron/purge | Daily 03:00 UTC | Deletes webhook, audit, activity, and exclusion logs older than 90 days. |
| GET /api/health | On demand | Confirms the app can reach Postgres. No auth required. |
Cron routes require CRON_SECRET as x-cron-secret or a Bearer token. After retries are exhausted, alerts can fire to Slack or n8n when those webhook URLs are set.
Security
- Instantly webhooks are verified with HMAC-SHA256 against
WEBHOOK_SECRET. - HubSpot exclusion webhooks use HubSpot’s signature (preferred via
HUBSPOT_CLIENT_SECRET) or the sharedWEBHOOK_SECRETheader. - Dashboard APIs require
x-api-key: DASHBOARD_API_KEY. - Contact emails are encrypted at rest with AES-256-GCM (
PII_ENCRYPTION_KEY) and redacted in application logs.
Troubleshooting
| Symptom | What to check |
|---|---|
| Signature verification failed | Instantly’s webhook secret must match WEBHOOK_SECRET. |
| 401 on HubSpot exclusion webhook | Send WEBHOOK_SECRET as a request header, or configure HubSpot signature verification. |
| Contact not found | Create the contact in HubSpot before Instantly emails them. The integration will not create contacts. |
| Retry queue growing | Check HubSpot and Instantly status, API keys, and rate limits. Retries run every 5 minutes. |
| Dashboard will not unlock | Use the current DASHBOARD_API_KEY. A 401 means the key does not match. |
For stack traces, use Sentry and Vercel runtime logs. Correlation IDs on the dashboard error table match those log entries.