agentsclimarketplace

Apollo webhooks events

Skill jeremylongshore/claude-code-plugins-plus-skills/plugins/saas-packs/apollo-pack/skills/apollo-webhooks-events

'Implement Apollo.io webhook and event-driven integrations.From its SKILL.md

Install
npx -y skills add jeremylongshore/claude-code-plugins-plus-skills --skill apollo-webhooks-events

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its file declares

Copied from the file, not written here

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

7.8 KB, ~1.8k tokens by cl100k_base, as published. Nobody here has run it

Apollo Webhooks & Events

Overview

Build event-driven integrations with Apollo.io. Apollo does not have a native webhook system like Stripe — instead, you build real-time sync by polling the API for changes or using third-party webhook platforms (Zapier, Pipedream, Make) that trigger on Apollo events. This skill covers both approaches plus the Contact Stages and Tasks APIs for workflow automation.

Prerequisites

  • Apollo account with master API key
  • Node.js 18+ with Express
  • For polling: a scheduler (cron, Cloud Scheduler, BullMQ)
  • For webhook platforms: Zapier/Pipedream/Make account

Instructions

Step 1: Poll for Contact Changes

Since Apollo lacks native webhooks, poll the Contacts Search API for recently updated records.

// src/sync/contact-poller.ts
import axios from 'axios';

const client = axios.create({
  baseURL: 'https://api.apollo.io/api/v1',
  headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.APOLLO_API_KEY! },
});

interface SyncState {
  lastSyncAt: string;  // ISO timestamp
}

export async function pollContactChanges(state: SyncState) {
  const { data } = await client.post('/contacts/search', {
    sort_by_field: 'contact_updated_at',
    sort_ascending: false,
    per_page: 100,
  });

  const lastSync = new Date(state.lastSyncAt);
  const newChanges = data.contacts.filter(
    (c: any) => new Date(c.updated_at) > lastSync,
  );

  if (newChanges.length > 0) {
    console.log(`Found ${newChanges.length} contact changes since ${state.lastSyncAt}`);
    for (const contact of newChanges) {
      await handleContactChange(contact);
    }
    state.lastSyncAt = new Date().toISOString();
  }

  return newChanges;
}

async function handleContactChange(contact: any) {
  console.log(`Contact updated: ${contact.name} (${contact.email}) — stage: ${contact.contact_stage_id}`);
  // Sync to your CRM, database, or notification system
}

Step 2: Track Contact Stage Changes

Apollo has a Contact Stages system. Use the List Contact Stages endpoint to get stage IDs, then filter contacts by stage.

// src/sync/stage-tracker.ts
export async function getContactStages() {
  const { data } = await client.get('/contact_stages');
  return data.contact_stages.map((s: any) => ({
    id: s.id,
    name: s.name,
    order: s.display_order,
  }));
}

// Find contacts that moved to a specific stage
export async function getContactsByStage(stageId: string) {
  const { data } = await client.post('/contacts/search', {
    contact_stage_ids: [stageId],
    sort_by_field: 'contact_updated_at',
    sort_ascending: false,
    per_page: 50,
  });
  return data.contacts;
}

// Update a contact's stage
export async function updateContactStage(contactId: string, stageId: string) {
  await client.put(`/contacts/${contactId}`, {
    contact_stage_id: stageId,
  });
}

Step 3: Monitor Sequence Engagement

Poll sequence data for replies, bounces, and engagement events.

// src/sync/sequence-monitor.ts
export async function checkSequenceEngagement(sequenceId: string) {
  const { data } = await client.post('/emailer_campaigns/search', {
    ids: [sequenceId],
    per_page: 1,
  });

  const seq = data.emailer_campaigns?.[0];
  if (!seq) return null;

  return {
    name: seq.name,
    active: seq.active,
    metrics: {
      scheduled: seq.unique_scheduled ?? 0,
      delivered: seq.unique_delivered ?? 0,
      opened: seq.unique_opened ?? 0,
      replied: seq.unique_replied ?? 0,
      bounced: seq.unique_bounced ?? 0,
      unsubscribed: seq.unique_unsubscribed ?? 0,
    },
    rates: {
      openRate: pct(seq.unique_opened, seq.unique_delivered),
      replyRate: pct(seq.unique_replied, seq.unique_delivered),
      bounceRate: pct(seq.unique_bounced, seq.unique_scheduled),
    },
  };
}

function pct(num?: number, denom?: number): string {
  if (!denom) return '0%';
  return `${(((num ?? 0) / denom) * 100).toFixed(1)}%`;
}

Step 4: Create Tasks for Follow-Up Actions

Use Apollo's Tasks API to create actionable follow-ups triggered by your polling logic.

// src/sync/task-creator.ts
export async function createFollowUpTask(params: {
  contactId: string;
  type: 'call' | 'action_item' | 'email';
  dueDate: string;
  note: string;
  assigneeId?: string;
}) {
  const { data } = await client.post('/tasks', {
    contact_id: params.contactId,
    type: params.type,
    due_date: params.dueDate,
    note: params.note,
    user_id: params.assigneeId,  // Apollo user ID to assign to
    priority: 'high',
    status: 'open',
  });

  return { taskId: data.task.id, type: data.task.type, dueDate: data.task.due_date };
}

// Example: auto-create call task when a sequence reply is detected
async function onSequenceReply(contactId: string, sequenceName: string) {
  await createFollowUpTask({
    contactId,
    type: 'call',
    dueDate: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString().split('T')[0],
    note: `Reply detected on sequence "${sequenceName}". Follow up immediately.`,
  });
}

Step 5: Set Up a Cron-Based Sync Service

// src/sync/scheduler.ts
import cron from 'node-cron';
import { pollContactChanges } from './contact-poller';
import { checkSequenceEngagement } from './sequence-monitor';

const syncState = { lastSyncAt: new Date(Date.now() - 60 * 60 * 1000).toISOString() };

// Poll every 5 minutes for contact changes
cron.schedule('*/5 * * * *', async () => {
  try {
    const changes = await pollContactChanges(syncState);
    console.log(`Sync complete: ${changes.length} changes`);
  } catch (err) {
    console.error('Contact sync failed:', err);
  }
});

// Check sequence engagement every 15 minutes
cron.schedule('*/15 * * * *', async () => {
  const activeSequenceIds = ['seq-1', 'seq-2'];  // from your config
  for (const id of activeSequenceIds) {
    const stats = await checkSequenceEngagement(id);
    if (stats && parseInt(stats.rates.replyRate) > 10) {
      console.log(`High reply rate on "${stats.name}": ${stats.rates.replyRate}`);
    }
  }
});

console.log('Apollo sync scheduler started');

Output

  • Contact change poller with timestamp-based incremental sync
  • Contact stage tracking and updates via the Stages API
  • Sequence engagement monitoring with reply/bounce/open rates
  • Task creation API for automated follow-ups
  • Cron-based scheduler for periodic sync

Error Handling

IssueResolution
Missed changes between pollsReduce poll interval or use sort_by_field: contact_updated_at
Rate limited during pollingAdd backoff, reduce per_page, increase poll interval
Duplicate task creationTrack processed contact IDs in a Set or database
Third-party webhook delaysZapier/Pipedream polling intervals vary (1-15 min on free tiers)

Resources

Next Steps

Proceed to apollo-performance-tuning for optimization.

What ships with it: 1 file

11.3 KB alongside SKILL.md

references/

Keep looking

Skills are one crate of 326,144. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.