Claude-code-plugins instantly-webhooks-events
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/instantly-pack/skills/instantly-webhooks-events" ~/.claude/skills/jeremylongshore-claude-code-plugins-instantly-webhooks-events && rm -rf "$T"
manifest:
plugins/saas-packs/instantly-pack/skills/instantly-webhooks-events/SKILL.mdsource content
Instantly Webhooks & Events
Overview
Handle Instantly API v2 webhooks for real-time email outreach event notifications. Instantly fires events when emails are sent, opened, clicked, replied to, or bounced, and when leads change interest status. Webhooks require Hypergrowth plan ($97/mo) or higher. Delivery retries: 3 times within 30 seconds on failure.
Prerequisites
- Instantly Hypergrowth plan or higher (required for webhooks)
- API key with
or appropriate webhook scopesall:all - Public HTTPS endpoint for receiving webhook payloads
environment variable setINSTANTLY_API_KEY
Webhook Event Types
| Event Type | Trigger | Key Payload Fields |
|---|---|---|
| Email delivered to recipient | , , |
| Recipient opens email | , , |
| Recipient clicks a link | , , |
| Recipient replies | , , |
| Email bounces | , , |
| Lead unsubscribes | , |
| All leads in campaign processed | , |
| Sending account error | , |
| Lead marked interested | , |
| Lead marked not interested | , |
| Meeting booked | , |
| Meeting completed | |
| Lead closed/won | |
| OOO reply detected | |
| Wrong person response | |
| Subscribe to everything | Varies by event |
Instructions
Step 1: Create Webhook via API
import { instantly } from "./src/instantly"; async function createWebhook() { // Create webhook for specific events const webhook = await instantly<{ id: string; name: string }>("/webhooks", { method: "POST", body: JSON.stringify({ name: "CRM Sync — Replies & Meetings", target_hook_url: "https://api.yourapp.com/webhooks/instantly", event_type: "reply_received", headers: { "X-Webhook-Secret": process.env.INSTANTLY_WEBHOOK_SECRET, }, }), }); console.log(`Webhook created: ${webhook.id}`); // Create additional webhooks for other events for (const event of ["lead_interested", "lead_meeting_booked", "email_bounced"]) { await instantly("/webhooks", { method: "POST", body: JSON.stringify({ name: `CRM Sync — ${event}`, target_hook_url: "https://api.yourapp.com/webhooks/instantly", event_type: event, headers: { "X-Webhook-Secret": process.env.INSTANTLY_WEBHOOK_SECRET }, }), }); } // Or subscribe to ALL events with one webhook await instantly("/webhooks", { method: "POST", body: JSON.stringify({ name: "All Events Monitor", target_hook_url: "https://api.yourapp.com/webhooks/instantly/all", event_type: "all_events", headers: { "X-Webhook-Secret": process.env.INSTANTLY_WEBHOOK_SECRET }, }), }); }
Step 2: Build Event Handler
import express from "express"; const app = express(); app.use(express.json()); app.post("/webhooks/instantly", async (req, res) => { // Validate secret if (req.headers["x-webhook-secret"] !== process.env.INSTANTLY_WEBHOOK_SECRET) { return res.status(401).json({ error: "Unauthorized" }); } // Respond 200 immediately — Instantly retries 3x in 30s on failure res.status(200).json({ received: true }); const { event_type, data } = req.body; console.log(`Event: ${event_type}`, JSON.stringify(data).slice(0, 300)); try { await routeEvent(event_type, data); } catch (err) { console.error(`Failed to process ${event_type}:`, err); } }); async function routeEvent(eventType: string, data: any) { switch (eventType) { case "reply_received": await handleReply(data); break; case "email_bounced": await handleBounce(data); break; case "lead_interested": case "lead_meeting_booked": case "lead_closed": await handlePositiveOutcome(eventType, data); break; case "lead_unsubscribed": await handleUnsubscribe(data); break; case "campaign_completed": await handleCampaignComplete(data); break; case "account_error": await handleAccountError(data); break; default: console.log(`Unhandled event: ${eventType}`); } }
Step 3: Implement Event Handlers
async function handleReply(data: { lead_email: string; campaign_id: string; reply_text: string; }) { console.log(`Reply from ${data.lead_email} in campaign ${data.campaign_id}`); // Sync to CRM await crmClient.updateContact(data.lead_email, { status: "replied", lastReply: data.reply_text, lastActivity: new Date(), }); // Notify sales team await slackNotify("#sales-replies", { text: `Reply from ${data.lead_email}:\n${data.reply_text.slice(0, 500)}`, }); } async function handleBounce(data: { lead_email: string; bounce_type: string; reason: string; }) { console.log(`Bounce: ${data.lead_email} (${data.bounce_type})`); if (data.bounce_type === "hard") { // Add to global block list await instantly("/block-lists-entries", { method: "POST", body: JSON.stringify({ bl_value: data.lead_email }), }); console.log(`Added ${data.lead_email} to block list`); } } async function handlePositiveOutcome( eventType: string, data: { lead_email: string; campaign_id: string } ) { const statusMap: Record<string, string> = { lead_interested: "interested", lead_meeting_booked: "meeting_scheduled", lead_closed: "closed_won", }; await crmClient.updateContact(data.lead_email, { status: statusMap[eventType] || eventType, lastActivity: new Date(), }); if (eventType === "lead_meeting_booked") { await slackNotify("#sales-wins", { text: `Meeting booked with ${data.lead_email}!`, }); } } async function handleUnsubscribe(data: { lead_email: string }) { // Add to block list to prevent future outreach across all campaigns await instantly("/block-lists-entries", { method: "POST", body: JSON.stringify({ bl_value: data.lead_email }), }); console.log(`Unsubscribed + blocked: ${data.lead_email}`); } async function handleCampaignComplete(data: { campaign_id: string }) { // Pull final analytics const analytics = await instantly(`/campaigns/analytics?id=${data.campaign_id}`); console.log(`Campaign complete:`, analytics); } async function handleAccountError(data: { email: string; error_type: string }) { console.error(`Account error: ${data.email} — ${data.error_type}`); await slackNotify("#ops-alerts", { text: `Instantly account error: ${data.email}\nType: ${data.error_type}`, }); }
Step 4: Manage Webhooks
// List all webhooks async function listWebhooks() { const webhooks = await instantly<Array<{ id: string; name: string; event_type: string; target_hook_url: string; }>>("/webhooks?limit=50"); for (const w of webhooks) { console.log(`${w.id}: ${w.name} [${w.event_type}] -> ${w.target_hook_url}`); } } // Test a webhook async function testWebhook(webhookId: string) { await instantly(`/webhooks/${webhookId}/test`, { method: "POST" }); } // Resume a paused webhook async function resumeWebhook(webhookId: string) { await instantly(`/webhooks/${webhookId}/resume`, { method: "POST" }); } // Check delivery status async function checkDeliveryHealth() { const summary = await instantly("/webhook-events/summary"); console.log("Webhook delivery summary:", summary); const byDate = await instantly("/webhook-events/summary-by-date"); console.log("By date:", byDate); } // Delete a webhook async function deleteWebhook(webhookId: string) { await instantly(`/webhooks/${webhookId}`, { method: "DELETE" }); }
Key API Endpoints
| Method | Path | Purpose |
|---|---|---|
| | Create webhook subscription |
| | List webhooks |
| | Update webhook |
| | Delete webhook |
| | Send test event |
| | Resume paused webhook |
| | List webhook events |
| | Delivery summary |
Error Handling
| Issue | Cause | Solution |
|---|---|---|
| No events delivered | Webhook not registered or paused | Check , resume if paused |
| Duplicate events | Retry delivery | Deduplicate by event ID + timestamp |
| Webhook paused automatically | Too many delivery failures | Fix endpoint, then |
| 30s timeout | Handler takes too long | Return 200 immediately, process async |
| Missing event_type | Using custom label events | Check field |
Resources
Next Steps
For performance optimization, see
instantly-performance-tuning.