CtrlK
BlogDocsLog inGet started
Tessl Logo

instantly-webhooks-events

Implement Instantly.ai webhook event handling with real API v2 event types. Use when setting up webhook endpoints, processing email events, or building CRM sync pipelines from Instantly notifications. Trigger with phrases like "instantly webhook", "instantly events", "instantly webhook handler", "handle instantly events", "instantly notifications".

68

Quality

83%

Does it follow best practices?

Impact

No eval scenarios have been run

SecuritybySnyk

Advisory

Suggest reviewing before use

SKILL.md
Quality
Evals
Security

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 all:all or appropriate webhook scopes
  • Public HTTPS endpoint for receiving webhook payloads
  • INSTANTLY_API_KEY environment variable set

Webhook Event Types

Event TypeTriggerKey Payload Fields
email_sentEmail delivered to recipientlead_email, campaign_id, step
email_openedRecipient opens emaillead_email, campaign_id, open_count
email_link_clickedRecipient clicks a linklead_email, campaign_id, link_url
reply_receivedRecipient replieslead_email, campaign_id, reply_text
email_bouncedEmail bounceslead_email, bounce_type, reason
lead_unsubscribedLead unsubscribeslead_email, campaign_id
campaign_completedAll leads in campaign processedcampaign_id, campaign_name
account_errorSending account erroremail, error_type
lead_interestedLead marked interestedlead_email, campaign_id
lead_not_interestedLead marked not interestedlead_email, campaign_id
lead_meeting_bookedMeeting bookedlead_email, campaign_id
lead_meeting_completedMeeting completedlead_email
lead_closedLead closed/wonlead_email
lead_out_of_officeOOO reply detectedlead_email
lead_wrong_personWrong person responselead_email
all_eventsSubscribe to everythingVaries 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

MethodPathPurpose
POST/webhooksCreate webhook subscription
GET/webhooksList webhooks
PATCH/webhooks/{id}Update webhook
DELETE/webhooks/{id}Delete webhook
POST/webhooks/{id}/testSend test event
POST/webhooks/{id}/resumeResume paused webhook
GET/webhook-eventsList webhook events
GET/webhook-events/summaryDelivery summary

Error Handling

IssueCauseSolution
No events deliveredWebhook not registered or pausedCheck GET /webhooks, resume if paused
Duplicate eventsRetry deliveryDeduplicate by event ID + timestamp
Webhook paused automaticallyToo many delivery failuresFix endpoint, then POST /webhooks/{id}/resume
30s timeoutHandler takes too longReturn 200 immediately, process async
Missing event_typeUsing custom label eventsCheck custom_interest_value field

Resources

  • Instantly Webhook API
  • Instantly Webhook Events
  • Instantly Blog: Webhooks Guide

Next Steps

For performance optimization, see instantly-performance-tuning.

Repository
jeremylongshore/claude-code-plugins-plus-skills
Last updated
Created

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.