Events API
List and fetch the events behind webhooks, for polling, backfills and reconciliation.
1 min read
Every change that produces a webhook also produces an event you can read through the API, whether or not you have an endpoint. Events are kept for 30 days.
| Request | |
|---|---|
GET /v1/events | List events, newest first. |
GET /v1/events/{event_id} | One event. |
GET /v1/event-types | The catalog of event types with descriptions and examples. No authentication. |
Reading events needs the webhooks:read scope.
Filters#
| Parameter | |
|---|---|
type | One type (campaign.updated) or a whole domain (campaign.*). |
created_gte | Created at or after this time (ISO 8601 or Unix seconds). |
created_lte | Created at or before this time (ISO 8601 or Unix seconds). |
limit, cursor | Standard pagination. |
Polling instead of webhooks#
If you cannot receive webhooks (a desktop tool, a locked-down network, a notebook), poll the Events API. Remember the newest created you processed and ask for everything since:
let since = await loadCheckpoint(); // e.g. '2026-10-14T09:00:00Z'
const qs = new URLSearchParams({ created_gte: since, limit: '100' });
const res = await fetch(`https://api.listener.com/v1/events?${qs}`, {
headers: { Authorization: `Bearer ${process.env.LISTENER_API_KEY}` },
});
const { data } = await res.json();
// Newest first: process oldest first, skip ids you have already seen.
for (const event of data.reverse()) {
if (await alreadyProcessed(event.id)) continue;
await handle(event);
since = event.created;
}
await saveCheckpoint(since);Use created_gte (inclusive) together with id deduplication, so an event created in the same second as your checkpoint is never skipped. Page through with next_cursor when has_more is true.
Polling every minute or two is plenty: changes made in the apps are detected every five minutes.
Reconciliation#
Even with webhooks, run the same loop on a schedule (for example hourly over the last day). It catches anything your endpoint missed while it was down or disabled.