Skip to content

Events & Webhooks Guide

meta-ads-collector emits lifecycle events during collection that you can hook into with callbacks or forward to external services via webhooks.

Event types

Event Constant String Value Data Keys When
COLLECTION_STARTED "collection_started" query, country, ad_type, status, search_type, page_ids, max_results Search begins
AD_COLLECTED "ad_collected" ad (Ad object) Each ad is collected
PAGE_FETCHED "page_fetched" page_number, ads_on_page, has_next_page API page is fetched
ERROR_OCCURRED "error_occurred" exception, context An error occurs
RATE_LIMITED "rate_limited" wait_seconds, retry_count Rate limiting detected
SESSION_REFRESHED "session_refreshed" reason Session is refreshed
COLLECTION_FINISHED "collection_finished" total_ads, total_pages, duration_seconds Search completes

Registering callbacks

Using event_emitter.on()

from meta_ads_collector import MetaAdsCollector, AD_COLLECTED, COLLECTION_FINISHED

def on_ad(event):
    ad = event.data["ad"]
    print(f"Collected: {ad.id} from {ad.page.name if ad.page is not None else 'Unknown page'}")

def on_finished(event):
    data = event.data
    print(f"Done: {data['total_ads']} ads in {data['duration_seconds']:.1f}s")

with MetaAdsCollector() as collector:
    collector.event_emitter.on(AD_COLLECTED, on_ad)
    collector.event_emitter.on(COLLECTION_FINISHED, on_finished)

    for ad in collector.search(query="test", max_results=10):
        pass

Using the callbacks parameter

Register callbacks at collector initialization:

from meta_ads_collector import MetaAdsCollector

def on_ad(event):
    print(f"Collected: {event.data['ad'].id}")

def on_finished(event):
    print(f"Finished: {event.data['total_ads']} ads")

with MetaAdsCollector(callbacks={
    "ad_collected": on_ad,
    "error_occurred": lambda event: print(f"Error: {event.data['context']}"),
    "collection_finished": on_finished,
}) as collector:
    for ad in collector.search(query="test", max_results=10):
        pass

Removing callbacks

Use off() on the emitter where the callback was registered:

from meta_ads_collector import MetaAdsCollector, AD_COLLECTED

def on_ad(event):
    print(event.data["ad"].id)

with MetaAdsCollector() as collector:
    collector.event_emitter.on(AD_COLLECTED, on_ad)
    collector.event_emitter.off(AD_COLLECTED, on_ad)

Event object

Each callback receives an Event object:

from meta_ads_collector import Event

# Construct an example event to inspect the event model.
event = Event(event_type="ad_collected", data={"ad": None})
print(event.event_type)  # e.g., "ad_collected"
print(event.data)        # event-specific payload
print(event.timestamp)   # UTC timestamp

Exception isolation

Callbacks are exception-isolated. If a callback raises an exception, it is logged as a warning but does not crash the collection pipeline. Other callbacks and the collection continue normally.

Stream mode

The stream() method yields (event_type, data) tuples for all lifecycle events through a single iterator:

from meta_ads_collector import MetaAdsCollector

with MetaAdsCollector() as collector:
    for event_type, data in collector.stream(query="test", max_results=10):
        if event_type == "collection_started":
            print(f"Starting search for: {data['query']}")
        elif event_type == "ad_collected":
            print(f"Ad: {data['ad'].id}")
        elif event_type == "page_fetched":
            print(f"Page {data['page_number']}: {data['ads_on_page']} ads")
        elif event_type == "rate_limited":
            print(f"Rate limited, waiting {data['wait_seconds']:.0f}s")
        elif event_type == "collection_finished":
            print(f"Finished: {data['total_ads']} ads in {data['duration_seconds']:.1f}s")

Webhooks

WebhookSender

POST ad data to an external HTTP endpoint as JSON:

from meta_ads_collector import MetaAdsCollector, WebhookSender, AD_COLLECTED

sender = WebhookSender(
    url="https://hooks.example.com/ads",
    retries=3,       # Retry up to 3 times on failure
    batch_size=1,    # Send immediately (no batching)
    timeout=10,      # 10 second timeout per request
)

with MetaAdsCollector() as collector:
    collector.event_emitter.on(AD_COLLECTED, sender.as_callback())
    for ad in collector.search(query="test", max_results=10):
        pass  # Each ad is POSTed to the webhook URL

Batch mode

Buffer ads and send them in batches:

from meta_ads_collector import MetaAdsCollector, WebhookSender, AD_COLLECTED

sender = WebhookSender(
    url="https://hooks.example.com/ads",
    batch_size=10,  # Send every 10 ads
)

with MetaAdsCollector() as collector:
    collector.event_emitter.on(AD_COLLECTED, sender.as_callback())
    for ad in collector.search(query="test", max_results=100):
        pass

    # Flush remaining buffered ads
    sender.flush()

Manual webhook sends

from meta_ads_collector import WebhookSender

sender = WebhookSender(url="https://hooks.example.com/ads")

# Send a single payload
success = sender.send({"ad_id": "12345", "page": "Test Page"})

# Send a batch of JSON-serializable payloads
success = sender.send_batch([
    {"ad_id": "12345", "page": "Test Page"},
    {"ad_id": "67890", "page": "Another Page"},
])

CLI webhook

meta-ads-collector -q "test" --webhook-url "https://hooks.example.com/ads" -o ads.json

Retry behavior

WebhookSender retries failed POST requests with exponential backoff (0.1s * 2^attempt). All methods are safe -- they catch exceptions internally and return True/False instead of raising.