Weezly Outreach
|Developer Docs

Webhook Events

Subscribe to events and receive real-time HTTP POST callbacks to your endpoint. Each event includes a signed JSON payload with full context.

HTTP Headers

Every webhook POST request includes these headers:

HeaderDescription
Content-TypeAlways application/json
X-Webhook-SignatureHMAC-SHA256 signature: t=timestamp,v1=hash
X-Webhook-EventThe event type, e.g. lead.replied
User-AgentAlways WeezlyOutreach/1.0

Response Requirements

Your endpoint must respond with a 2xx status code within 10 seconds. Non-2xx responses or timeouts are treated as failures. After 10 consecutive failures, the webhook is automatically disabled. You can re-enable it from Settings → API.

Verifying Signatures

Every webhook delivery includes an X-Webhook-Signature header. Verify this signature to ensure the payload was sent by Weezly Outreach and hasn't been tampered with.

Node.js — Verify signatureJavaScript
const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const parts = {};
  signature.split(',').forEach(part => {
    const [key, value] = part.split('=');
    parts[key] = value;
  });

  const timestamp = parts['t'];
  const expectedSig = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${payload}`)
    .digest('hex');

  return parts['v1'] === expectedSig;
}

app.post('/webhook', (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const isValid = verifyWebhookSignature(
    JSON.stringify(req.body),
    signature,
    process.env.WEBHOOK_SECRET
  );

  if (!isValid) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const { event, data } = req.body;
  console.log(`Received event: ${event}`, data);
  res.status(200).json({ received: true });
});
Python — Verify signaturePython
import hmac
import hashlib
from flask import Flask, request, jsonify

app = Flask(__name__)

def verify_signature(payload: str, signature: str, secret: str) -> bool:
    parts = dict(p.split('=', 1) for p in signature.split(','))
    timestamp = parts.get('t', '')
    expected = hmac.new(
        secret.encode(),
        f"{timestamp}.{payload}".encode(),
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(parts.get('v1', ''), expected)

@app.route('/webhook', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-Webhook-Signature', '')
    payload = request.get_data(as_text=True)

    if not verify_signature(payload, signature, WEBHOOK_SECRET):
        return jsonify(error='Invalid signature'), 401

    data = request.get_json()
    print(f"Event: {data['event']}", data['data'])
    return jsonify(received=True), 200

Payload Structure

Every webhook payload follows the same top-level structure. The data field contains event-specific fields documented below.

Base payload structureJSON
{
  "event": "event.type",
  "data": {
    "...event-specific fields": "..."
  },
  "timestamp": "2026-06-05T14:32:01.234Z",
  "webhookId": "cm3wh789pqr"
}

Campaign Step Types

The step.type field in campaign.step.completed and campaign.step.failed events will be one of:

TypeDescription
CONNECTION_REQUESTConnection Request
MESSAGEDirect Message
VIDEO_MESSAGEVideo Message
VOICE_NOTEVoice Note
INMAILInMail
VIEW_PROFILEProfile View
LIKE_POSTLike Post
REACT_TO_POSTReact to Post
COMMENT_ON_POSTComment on Post
FOLLOW_PROFILEFollow Profile

Event Reference

lead.repliedCampaigns

Fired when a lead sends a reply message to your outreach. This event triggers immediately when the reply is detected.

Example payload
POSTyour-endpoint.com/webhook
{
  "event": "lead.replied",
  "data": {
    "campaignId": "cm3abc123def456",
    "campaignName": "Q1 SaaS Founders Outreach",
    "executionId": "cm3exec789ghi",
    "leadId": "cm3lead456jkl",
    "leadName": "Sarah Chen",
    "lead": {
      "id": "cm3lead456jkl",
      "firstName": "Sarah",
      "lastName": "Chen",
      "fullName": "Sarah Chen",
      "headline": "CEO at TechStartup",
      "jobTitle": "CEO",
      "company": "TechStartup Inc.",
      "email": "sarah@techstartup.com",
      "phone": null,
      "location": "San Francisco, CA",
      "linkedinUrl": "https://linkedin.com/in/sarahchen",
      "linkedinPublicId": "sarahchen",
      "photoUrl": "https://media.licdn.com/.../sarah.jpg",
      "connectionStatus": "CONNECTED",
      "source": "SEARCH"
    },
    "accountId": "cm3acc123mno",
    "accountName": "John Smith",
    "message": {
      "text": "Hi John, thanks for reaching out! I'd love to chat about this.",
      "chatId": "chat_abc123def456",
      "receivedAt": "2026-06-05T14:32:00.000Z"
    }
  },
  "timestamp": "2026-06-05T14:32:01.234Z",
  "webhookId": "cm3wh789pqr"
}
Handle this eventNode.js
// When a lead replies to your outreach
app.post('/webhook', (req, res) => {
  const { event, data } = req.body;

  if (event === 'lead.replied') {
    const { lead, message, campaignName } = data;

    console.log(`${lead.fullName} replied to "${campaignName}"`);
    console.log(`Message: ${message.text}`);

    // Example: Send Slack notification
    await slack.chat.postMessage({
      channel: '#sales-replies',
      text: `🔔 ${lead.fullName} (${lead.company}) replied!\n> ${message.text}`,
    });

    // Example: Update your CRM
    await crm.contacts.update(lead.email, {
      status: 'replied',
      lastActivity: message.receivedAt,
    });
  }

  res.status(200).json({ received: true });
});
connection.acceptedCampaigns

Fired when a lead accepts your LinkedIn connection request. Includes the lead's profile data and the campaign context.

Example payload
POSTyour-endpoint.com/webhook
{
  "event": "connection.accepted",
  "data": {
    "campaignId": "cm3abc123def456",
    "campaignName": "Q1 SaaS Founders Outreach",
    "executionId": "cm3exec789ghi",
    "leadId": "cm3lead456jkl",
    "leadName": "Marcus Johnson",
    "lead": {
      "id": "cm3lead456jkl",
      "firstName": "Marcus",
      "lastName": "Johnson",
      "fullName": "Marcus Johnson",
      "headline": "VP Engineering at ScaleUp",
      "jobTitle": "VP Engineering",
      "company": "ScaleUp Solutions",
      "email": "marcus@scaleup.com",
      "phone": null,
      "location": "Austin, TX",
      "linkedinUrl": "https://linkedin.com/in/marcusjohnson",
      "linkedinPublicId": "marcusjohnson",
      "photoUrl": "https://media.licdn.com/.../marcus.jpg",
      "connectionStatus": "CONNECTED",
      "source": "SEARCH"
    },
    "accountId": "cm3acc123mno",
    "accountName": "John Smith",
    "acceptedAt": "2026-06-05T09:15:00.000Z"
  },
  "timestamp": "2026-06-05T09:15:02.456Z",
  "webhookId": "cm3wh789pqr"
}
Handle this eventNode.js
// When a lead accepts your connection request
app.post('/webhook', (req, res) => {
  const { event, data } = req.body;

  if (event === 'connection.accepted') {
    const { lead, campaignName, accountName } = data;

    console.log(`${lead.fullName} accepted connection from ${accountName}`);

    // Example: Add to CRM pipeline
    await crm.deals.create({
      contact: lead.fullName,
      company: lead.company,
      source: `LinkedIn - ${campaignName}`,
      stage: 'connected',
    });
  }

  res.status(200).json({ received: true });
});
campaign.completedCampaigns

Fired when all leads in a campaign have reached a terminal state (completed, replied, or failed). Includes final campaign statistics.

Example payload
POSTyour-endpoint.com/webhook
{
  "event": "campaign.completed",
  "data": {
    "campaignId": "cm3abc123def456",
    "campaignName": "Q1 SaaS Founders Outreach",
    "stats": {
      "totalLeads": 150,
      "completed": 120,
      "replied": 18,
      "failed": 12,
      "connectionsSent": 150,
      "connectionsAccepted": 45,
      "messagesSent": 45,
      "connectionRate": 30,
      "replyRate": 12
    },
    "startedAt": "2026-05-20T08:00:00.000Z",
    "completedAt": "2026-06-05T16:00:00.000Z"
  },
  "timestamp": "2026-06-05T16:00:05.789Z",
  "webhookId": "cm3wh789pqr"
}
Handle this eventNode.js
// When a campaign finishes all leads
app.post('/webhook', (req, res) => {
  const { event, data } = req.body;

  if (event === 'campaign.completed') {
    const { campaignName, stats } = data;

    console.log(`Campaign "${campaignName}" completed!`);
    console.log(`Results: ${stats.replied} replies from ${stats.totalLeads} leads`);
    console.log(`Connection rate: ${stats.connectionRate}%`);
    console.log(`Reply rate: ${stats.replyRate}%`);

    // Example: Send summary email
    await email.send({
      to: 'team@company.com',
      subject: `Campaign "${campaignName}" — ${stats.replyRate}% reply rate`,
      body: `Completed with ${stats.replied} replies from ${stats.totalLeads} leads.`,
    });
  }

  res.status(200).json({ received: true });
});
campaign.launchedCampaigns

Fired when a campaign is launched and begins execution. Includes the campaign configuration and total lead count.

Example payload
POSTyour-endpoint.com/webhook
{
  "event": "campaign.launched",
  "data": {
    "campaignId": "cm3abc123def456",
    "campaignName": "Q1 SaaS Founders Outreach",
    "totalLeads": 150,
    "senderCount": 3,
    "senders": [
      {
        "accountId": "cm3acc1",
        "name": "John Smith"
      },
      {
        "accountId": "cm3acc2",
        "name": "Jane Doe"
      },
      {
        "accountId": "cm3acc3",
        "name": "Mike Wilson"
      }
    ],
    "stepCount": 5,
    "launchedAt": "2026-06-01T08:00:00.000Z"
  },
  "timestamp": "2026-06-01T08:00:01.234Z",
  "webhookId": "cm3wh789pqr"
}
Handle this eventNode.js
// When a campaign starts executing
app.post('/webhook', (req, res) => {
  const { event, data } = req.body;

  if (event === 'campaign.launched') {
    const { campaignName, totalLeads, senderCount } = data;

    console.log(`Campaign "${campaignName}" launched!`);
    console.log(`${totalLeads} leads across ${senderCount} senders`);
  }

  res.status(200).json({ received: true });
});
campaign.step.completedCampaigns

Fired each time a campaign step is successfully executed for a lead. High-volume event — fires once per lead per step.

Example payload
POSTyour-endpoint.com/webhook
{
  "event": "campaign.step.completed",
  "data": {
    "campaignId": "cm3abc123def456",
    "campaignName": "Q1 SaaS Founders Outreach",
    "executionId": "cm3exec789ghi",
    "leadId": "cm3lead456jkl",
    "lead": {
      "firstName": "Sarah",
      "lastName": "Chen",
      "fullName": "Sarah Chen",
      "company": "TechStartup Inc.",
      "linkedinUrl": "https://linkedin.com/in/sarahchen"
    },
    "step": {
      "type": "CONNECTION_REQUEST",
      "order": 1,
      "messageText": "Hi Sarah, I noticed we're both in the SaaS space..."
    },
    "accountId": "cm3acc123mno",
    "accountName": "John Smith",
    "executedAt": "2026-06-02T10:30:00.000Z"
  },
  "timestamp": "2026-06-02T10:30:01.567Z",
  "webhookId": "cm3wh789pqr"
}
Handle this eventNode.js
// When a step executes for a lead
app.post('/webhook', (req, res) => {
  const { event, data } = req.body;

  if (event === 'campaign.step.completed') {
    const { lead, step, campaignName } = data;

    console.log(`${step.type} completed for ${lead.fullName}`);

    // Example: Log to analytics
    await analytics.track('outreach_step', {
      campaign: campaignName,
      stepType: step.type,
      lead: lead.fullName,
    });
  }

  res.status(200).json({ received: true });
});
campaign.step.failedCampaigns

Fired when a campaign step fails for a lead after all retry attempts. Includes error details for debugging.

Example payload
POSTyour-endpoint.com/webhook
{
  "event": "campaign.step.failed",
  "data": {
    "campaignId": "cm3abc123def456",
    "campaignName": "Q1 SaaS Founders Outreach",
    "executionId": "cm3exec789ghi",
    "leadId": "cm3lead456jkl",
    "lead": {
      "firstName": "Alex",
      "lastName": "Rivera",
      "fullName": "Alex Rivera",
      "company": "StartupCo",
      "linkedinUrl": "https://linkedin.com/in/alexrivera"
    },
    "step": {
      "type": "MESSAGE",
      "order": 3
    },
    "error": "Rate limit exceeded — account paused for 2 hours",
    "accountId": "cm3acc123mno",
    "accountName": "John Smith",
    "failedAt": "2026-06-03T14:20:00.000Z"
  },
  "timestamp": "2026-06-03T14:20:01.890Z",
  "webhookId": "cm3wh789pqr"
}
Handle this eventNode.js
// When a step fails for a lead
app.post('/webhook', (req, res) => {
  const { event, data } = req.body;

  if (event === 'campaign.step.failed') {
    const { lead, step, error, campaignName } = data;

    console.error(`Step ${step.type} failed for ${lead.fullName}: ${error}`);

    // Example: Alert on repeated failures
    await pagerduty.createIncident({
      title: `Campaign step failed: ${campaignName}`,
      body: `${step.type} failed for ${lead.fullName}: ${error}`,
    });
  }

  res.status(200).json({ received: true });
});
account.status_changedAccounts

Fired when a LinkedIn account status changes — paused due to rate limiting, warned by LinkedIn, or restored to active.

Example payload
POSTyour-endpoint.com/webhook
{
  "event": "account.status_changed",
  "data": {
    "accountId": "cm3acc123mno",
    "accountName": "John Smith",
    "previousStatus": "ACTIVE",
    "newStatus": "WARNED",
    "reason": "Unusual activity detected by LinkedIn",
    "changedAt": "2026-06-04T11:00:00.000Z"
  },
  "timestamp": "2026-06-04T11:00:02.345Z",
  "webhookId": "cm3wh789pqr"
}
Handle this eventNode.js
// When a LinkedIn account status changes
app.post('/webhook', (req, res) => {
  const { event, data } = req.body;

  if (event === 'account.status_changed') {
    const { accountName, previousStatus, newStatus, reason } = data;

    console.log(`Account "${accountName}": ${previousStatus} → ${newStatus}`);

    if (newStatus === 'WARNED' || newStatus === 'PAUSED') {
      // Example: Send urgent Slack alert
      await slack.chat.postMessage({
        channel: '#linkedin-alerts',
        text: `⚠️ Account "${accountName}" is now ${newStatus}!\nReason: ${reason}`,
      });
    }
  }

  res.status(200).json({ received: true });
});
lead.createdLeads

Fired when new leads are imported into the system — via search, CSV upload, or manual creation. Fires once per lead.

Example payload
POSTyour-endpoint.com/webhook
{
  "event": "lead.created",
  "data": {
    "leadId": "cm3lead456jkl",
    "lead": {
      "firstName": "Emily",
      "lastName": "Zhang",
      "fullName": "Emily Zhang",
      "headline": "Head of Growth at Acme Corp",
      "company": "Acme Corp",
      "email": "emily@acme.com",
      "linkedinUrl": "https://linkedin.com/in/emilyzhang",
      "location": "San Francisco, CA"
    },
    "listId": "cm3list789abc",
    "listName": "SaaS Founders — West Coast",
    "source": "SEARCH"
  },
  "timestamp": "2026-06-05T08:45:00.123Z",
  "webhookId": "cm3wh789pqr"
}
Handle this eventNode.js
// When new leads are imported
app.post('/webhook', (req, res) => {
  const { event, data } = req.body;

  if (event === 'lead.created') {
    const { lead, listName, source } = data;

    console.log(`New lead: ${lead.fullName} added to "${listName}" via ${source}`);

    // Example: Enrich lead data
    if (lead.email) {
      await enrichment.lookup(lead.email);
    }

    // Example: Sync to CRM
    await crm.contacts.create({
      name: lead.fullName,
      company: lead.company,
      email: lead.email,
      linkedinUrl: lead.linkedinUrl,
      source: `Outreach - ${listName}`,
    });
  }

  res.status(200).json({ received: true });
});

Using with Zapier, Make, or n8n

These automation platforms can receive webhooks directly. Simply use their webhook trigger URL as your endpoint:

Zapier

Create a new Zap → Choose "Webhooks by Zapier" as trigger → Select "Catch Hook" → Copy the webhook URL → Paste it in Settings → API → Webhooks.

Make (Integromat)

Create a new scenario → Add "Webhooks" module → Select "Custom webhook" → Copy the URL → Paste it in Settings → API → Webhooks.

n8n

Add a "Webhook" trigger node → Set method to POST → Copy the production URL → Paste it in Settings → API → Webhooks.

Delivery Behavior

Timeout10 seconds per delivery attempt
Success codesAny 2xx status code (200, 201, 202, 204)
Auto-disableAfter 10 consecutive failures, webhook is disabled
Delivery orderBest-effort chronological, not guaranteed
IdempotencyUse webhookId + timestamp to deduplicate