Webhooks

Register HTTPS endpoints in Settings → API Keys to receive real-time events. Every delivery is signed with HMAC-SHA256 and retried up to 3 times with exponential backoff.

Events

campaign.built

Fired when an AI campaign build finishes and the campaign is ready.

{
  "data": {
    "campaignId": "cmp_123",
    "name": "Spring Sale — Prospecting",
    "platform": "meta",
    "status": "active",
    "adSetCount": 3,
    "adCount": 6
  }
}
campaign.budget_adjusted

Fired when a campaign or ad set budget is changed (manually or by the optimizer).

{
  "data": {
    "campaignId": "cmp_123",
    "previousBudget": 50,
    "newBudget": 75,
    "currency": "USD",
    "source": "optimizer",
    "reason": "ROAS above target — scaling spend"
  }
}
creative.ready

Fired when an AI-generated creative finishes processing and is available.

{
  "data": {
    "creativeId": "crt_123",
    "campaignId": "cmp_123",
    "type": "image",
    "fileUrl": "https://cdn.lazyads.ai/creatives/crt_123.png",
    "predictedRoas": 2.4
  }
}
optimization.action_taken

Fired when the autonomous optimizer takes an action on a campaign.

{
  "data": {
    "campaignId": "cmp_123",
    "action": "pause_ad",
    "entityId": "ad_456",
    "detail": "CPA 3x above target for 5 days"
  }
}

Request format

Each delivery is a POST with a JSON body and these headers:

X-LazyAds-SignatureHex HMAC-SHA256 of `{timestamp}.{rawBody}` using your endpoint secret
X-LazyAds-TimestampUnix timestamp (seconds) the signature was computed at
X-LazyAds-EventThe event type, for routing without parsing the body
{
  "id": "whd_abc123",
  "type": "campaign.built",
  "createdAt": "2026-06-24T18:00:00.000Z",
  "data": {
    "campaignId": "cmp_123",
    "name": "Spring Sale — Prospecting",
    "platform": "meta",
    "status": "active",
    "adSetCount": 3,
    "adCount": 6
  }
}

Verifying signatures

Always verify the signature against the raw request body before acting on an event. Reject deliveries whose timestamp is more than a few minutes old to prevent replays.

import crypto from 'crypto';

// Express example — verify the signature before trusting the payload.
app.post('/webhooks/lazyads', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.header('X-LazyAds-Signature');
  const timestamp = req.header('X-LazyAds-Timestamp');
  const rawBody = req.body.toString('utf8');

  const expected = crypto
    .createHmac('sha256', process.env.LAZYADS_WEBHOOK_SECRET)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');

  const valid =
    signature &&
    crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));

  if (!valid) return res.status(400).send('invalid signature');

  const event = JSON.parse(rawBody);
  // handle event.type ...
  res.sendStatus(200);
});

Retries & delivery logs

A delivery is considered successful on any 2xx response. Non-2xx responses and timeouts are retried up to 3 attempts. Endpoints that fail repeatedly are automatically disabled — re-enable them from settings once fixed. Recent delivery attempts and their status codes are available per endpoint.

Zapier & n8n (REST Hooks)

Automation tools can self-register subscriptions with just an API key — no in-app setup needed. This follows the standard Zapier REST Hook pattern, and works the same in n8n, Make, or any HTTP node.

POST /api/v1/webhooksSubscribe — body { url, events? }. Returns the signing secret once.
GET /api/v1/webhooksList your active subscriptions.
DELETE /api/v1/webhooks/{id}Unsubscribe by id (or DELETE /api/v1/webhooks?url=…).
GET /api/v1/webhooks/eventsEvent catalog with sample payloads for field mapping.
# Subscribe (what Zapier/n8n calls to register a target URL)
curl -X POST https://app.lazyads.ai/api/v1/webhooks \
  -H "Authorization: Bearer $LAZYADS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://hooks.zapier.com/hooks/catch/123/abc",
    "events": ["campaign.built", "creative.ready"]
  }'
# → 201 { "data": { "id": "whe_…", "secret": "whsec_…", "events": [...] } }

# Unsubscribe
curl -X DELETE "https://app.lazyads.ai/api/v1/webhooks/whe_…" \
  -H "Authorization: Bearer $LAZYADS_API_KEY"

In Zapier, use a “Catch Hook” / REST Hook trigger and point the subscribe step at POST /api/v1/webhooks with the polling/target URL Zapier provides; map fields from GET /api/v1/webhooks/events. In n8n, use a Webhook node as the target and register its URL with the same endpoint. The API key needs the webhooks scope.