Watchlist events, pushed to your endpoint.

Configure a webhook URL on any watchlist and BrandMov POSTs a JSON envelope the moment we detect new ads or removals. Same envelope shape as Stripe or GitHub: an id, a type, an api_version, and a data object.

Envelope

Every event carries the same top-level shape. The event-specific payload lives under data.

{
  "id": "evt_5b1c8a3f0e2d4a1b9c7f3d21",
  "api_version": "2026-09-01",
  "type": "watchlist.new_ads",
  "created_at": "2026-09-02T09:00:00+00:00",
  "delivery": { "attempt": 1, "test": false },
  "data": { ... }
}
  • id: unique event id. Log it and dedupe on your side; a retried delivery reuses the same id.
  • api_version: the payload contract in effect at the time of send. Pin against this if you plan to parse defensively.
  • type: one of the events listed below.
  • delivery.test: true when the event was sent by the “Send test event” button on the dashboard, false on real detections.
  • data.watchlist: always carries workspace_id and workspace_name so one receiver can route across workspaces.

watchlist.new_ads

Fires when the nightly refresh finds ads on a watched page that weren't there last time. data.new_ads.items lists each new creative, with enough metadata to render a preview or hit the dashboard link.

{
  "id": "evt_5b1c8a3f0e2d4a1b9c7f3d21",
  "api_version": "2026-09-01",
  "type": "watchlist.new_ads",
  "created_at": "2026-09-02T09:00:00+00:00",
  "delivery": { "attempt": 1, "test": false },
  "data": {
    "watchlist": {
      "id": "wl_abc123",
      "brand": "Example Brand",
      "page_url": "https://www.facebook.com/examplebrand",
      "workspace_id": "1c9d2f4a-7b6e-4a2f-9c3d-8e5f1a2b3c4d",
      "workspace_name": "Acme Growth"
    },
    "new_ads": {
      "count": 2,
      "items": [
        {
          "ad_archive_id": "1234567890",
          "headline": "New spring drop is here",
          "cta_text": "Shop now",
          "image_url": "https://cdn.brandmov.com/...jpg",
          "page_name": "Example Brand",
          "start_date": "2026-09-01",
          "is_video": false
        }
      ]
    }
  }
}

watchlist.ad_removed

Fires when ads we'd previously seen on a watched page dropped out of the current pull. data.removed_ads.ad_archive_ids is a flat list of the archive ids that disappeared.

{
  "id": "evt_9d4c1b2e0f5a6b3c8d7e0f11",
  "api_version": "2026-09-01",
  "type": "watchlist.ad_removed",
  "created_at": "2026-09-02T21:00:00+00:00",
  "delivery": { "attempt": 1, "test": false },
  "data": {
    "watchlist": {
      "id": "wl_abc123",
      "brand": "Example Brand",
      "page_url": "https://www.facebook.com/examplebrand",
      "workspace_id": "1c9d2f4a-7b6e-4a2f-9c3d-8e5f1a2b3c4d",
      "workspace_name": "Acme Growth"
    },
    "removed_ads": {
      "count": 2,
      "ad_archive_ids": ["1234567890", "1234567891"]
    }
  }
}

Delivery & retries

  • POST, Content-Type: application/json, no auth header. Verify by allow-listing our sender IPs or the target URL secret you bake into the path.
  • A 2xx is a success. Any other status (or a timeout beyond ~10s) counts as a failure and enters retry.
  • Retries back off: a small burst in the first few minutes, then progressively longer gaps for ~24 hours. After that the event is dropped; watchlist state advances regardless.
  • Use the “Send test event” button on the Developer page to smoke-check your receiver without waiting for a real detection. It sends a real envelope with delivery.test = true.