Webhooks

Receive real-time notifications when campaign events occur.

Overview

Webhooks allow you to receive HTTP POST requests when events occur in your campaigns. Configure a webhook URL when creating a campaign or in your dashboard settings.

Configuration

Per-Campaign Webhook

POST /campaigns
{
  "mcp": "sales-outreach",
  "target_url": "acme.com",
  "reporting": {
    "webhook_url": "https://your-server.com/webhooks/mcpfactory"
  }
}

Global Webhook (Dashboard)

Go to Settings → Webhooks to configure a webhook that receives events from all campaigns.

Webhook Payload

All webhooks are sent as HTTP POST with JSON body:

POST https://your-server.com/webhooks/mcpfactory
Content-Type: application/json
X-MCPFactory-Signature: sha256=xxxxx

{
  "id": "evt_abc123",
  "event": "reply.received",
  "created_at": "2026-01-31T14:23:00Z",
  "campaign_id": "camp_abc123",
  "data": {
    // Event-specific data
  }
}

Events

campaign.started

Campaign has begun processing.

{
  "event": "campaign.started",
  "campaign_id": "camp_abc123",
  "data": {
    "mcp": "sales-outreach",
    "target_url": "acme.com"
  }
}

campaign.milestone

Reached a volume milestone (100, 500, 1000 emails).

{
  "event": "campaign.milestone",
  "campaign_id": "camp_abc123",
  "data": {
    "milestone": "100_emails",
    "stats": {
      "emails_sent": 100,
      "delivered": 94,
      "opened": 21,
      "replied": 3
    }
  }
}

campaign.paused

Campaign was paused (manually or due to budget).

{
  "event": "campaign.paused",
  "campaign_id": "camp_abc123",
  "data": {
    "reason": "budget_exceeded",
    "budget_spent_usd": 10.23,
    "budget_limit_usd": 10.00
  }
}

campaign.completed

Campaign finished (trial ended or schedule complete).

{
  "event": "campaign.completed",
  "campaign_id": "camp_abc123",
  "data": {
    "reason": "trial_ended",
    "final_stats": {
      "emails_sent": 487,
      "delivered": 456,
      "opened": 112,
      "replied": 23,
      "meetings_booked": 5
    },
    "total_cost_usd": 8.74
  }
}

reply.received

Someone replied to a campaign email.

{
  "event": "reply.received",
  "campaign_id": "camp_abc123",
  "data": {
    "reply_id": "reply_xyz789",
    "email": "john@prospect.com",
    "name": "John Smith",
    "company": "Prospect Inc",
    "title": "CTO",
    "sentiment": "positive",
    "preview": "Thanks for reaching out! I'd love to schedule a call to discuss...",
    "received_at": "2026-01-31T14:23:00Z"
  }
}

meeting.booked

A meeting was scheduled via calendar link.

{
  "event": "meeting.booked",
  "campaign_id": "camp_abc123",
  "data": {
    "email": "john@prospect.com",
    "name": "John Smith",
    "company": "Prospect Inc",
    "meeting_time": "2026-02-03T15:00:00Z",
    "calendar_link": "https://calendar.google.com/..."
  }
}

budget.warning

80% of daily/weekly/monthly budget used.

{
  "event": "budget.warning",
  "campaign_id": "camp_abc123",
  "data": {
    "budget_type": "daily",
    "spent_usd": 8.12,
    "limit_usd": 10.00,
    "percentage": 81
  }
}

Verifying Signatures

All webhooks include an X-MCPFactory-Signature header. Verify it to ensure the request is from MCP Factory:

import crypto from 'crypto';

function verifyWebhook(payload: string, signature: string, secret: string) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

Get your webhook secret from Dashboard → Settings → Webhooks.

Retries

If your endpoint returns a non-2xx status, we retry with exponential backoff:

  • Retry 1: 1 minute
  • Retry 2: 5 minutes
  • Retry 3: 30 minutes
  • Retry 4: 2 hours
  • Retry 5: 24 hours

After 5 failed retries, the webhook is marked as failed.

Testing

Use Dashboard → Webhooks → Test to send a test event to your endpoint.