Skip to content

Reference

Events

Every change that a webhook can deliver also exists as a record you can read. That is the difference between a webhook integration that recovers from an outage and one that quietly drifts: deliveries are a convenience, the event stream is the source of truth, and reading it forwards from a stored cursor is how you catch up.

The event object

An event is a type, a timestamp and the affected record. The record inside data has the same shape as the corresponding read endpoint returns, so a handler written against the events resource and a handler written against a webhook delivery parse the same thing.

FieldTypeDescription
idstringUnique identifier, prefixed event_. The same id appears in the webhook delivery.
objectstringAlways "event".
eventstringThe event type, in resource.verb form. See the table below for the full set.
dataobjectThe affected record, in the same shape the corresponding read endpoint returns.
organization_idstring | nullPresent when the event belongs to a tenant. Null for environment-wide events.
created_attimestampRFC 3339. Events are ordered by this field, and the order is stable.

Store the last id you processed

One column, updated after each successful handle. It is the cheapest piece of state in the integration and the only thing standing between a webhook outage and a manual reconciliation nobody has time for.

Event types

Subscribe to what you handle. An endpoint subscribed to every type is an endpoint whose handler starts with a long switch statement of empty branches, and whose failure count is driven by events nobody meant to receive.

TypeObjectWhat it means
connection.activatedConnectionAn SSO connection started accepting sign-ins. A good moment to tell the account team.
connection.deactivatedConnectionSign-ins through it are now refused. Often a certificate rotation gone wrong.
connection.deletedConnectionThe connection is gone. Existing sessions are not ended by this.
dsync.activatedDirectoryA directory finished linking and is now syncing.
dsync.user.createdDirectory userSomebody was added at the source. Create or reactivate them in your application.
dsync.user.updatedDirectory userAn attribute changed: name, title, custom attribute, or state.
dsync.user.deletedDirectory userRemoved at the source. Revoke access; do not necessarily delete their data.
dsync.group.createdDirectory groupA new group appeared. Relevant if you map groups to roles.
dsync.group.user_addedDirectory groupMembership changed. This is the event that grants access in most designs.
dsync.group.user_removedDirectory groupMembership changed the other way. Revoke on this one promptly.
role.createdRoleA role was defined in this environment.
role.updatedRoleA role's permission set changed. Sessions pick it up at their next refresh.

Endpoints

Paths are relative to https://api.paycux.com. To receive these events rather than poll for them, register an endpoint on the webhooks resource.

MethodPathWhat it does
GET/eventsRead the event stream directly, filtered by type, organization or range.
GET/events/:idRetrieve a single event by the id from a webhook delivery.
GET/events?after=:idWalk forwards from the last event you processed, after an outage or a deploy.

List events

This is the endpoint that makes webhook outages survivable. Record the id of the last event you processed, and after any incident replay forwards from it rather than guessing what you missed. Events are retained for 30 days.

GET /events
1curl "https://api.paycux.com/events?events[]=dsync.user.created&events[]=dsync.user.deleted&limit=2" \
2 -H "Authorization: Bearer sk_example_123456789"
response
1HTTP 200
2{
3 "object": "list",
4 "data": [
5 {
6 "object": "event",
7 "id": "event_01HQ8ZK3M4N5P6R7S8T9V0W1X2",
8 "event": "dsync.user.created",
9 "organization_id": "org_01HQ8ZK3M4N5P6R7S8T9V0W1X2",
10 "data": {
11 "object": "directory_user",
12 "id": "directory_user_01HQ8ZK3M4N5P6R7S8T9V0W1X2",
13 "directory_id": "directory_01HQ8ZK3M4N5P6R7S8T9V0W1X2",
14 "idp_id": "00u1a0ufowBJlzPlk357",
15 "emails": [{ "primary": true, "value": "avery@foo-corp.example" }],
16 "first_name": "Avery",
17 "last_name": "Lindqvist",
18 "state": "active"
19 },
20 "created_at": "2026-07-04T11:02:38.114Z"
21 }
22 ],
23 "list_metadata": { "before": null, "after": "event_01HQ8ZK3M4N5P6R7S8T9V0W1X3" }
24}

Retrieve an event

Fetch the event by id when a delivery arrives and you want to confirm it against the source rather than trusting the body. A delivery you cannot find here did not come from us, whatever its signature claimed.

GET /events/:id
1curl https://api.paycux.com/events/event_01HQ8ZK3M4N5P6R7S8T9V0W1X2 \
2 -H "Authorization: Bearer sk_example_123456789"
response
1HTTP 200
2{
3 "object": "event",
4 "id": "event_01HQ8ZK3M4N5P6R7S8T9V0W1X2",
5 "event": "dsync.group.user_removed",
6 "organization_id": "org_01HQ8ZK3M4N5P6R7S8T9V0W1X2",
7 "data": {
8 "object": "directory_group",
9 "id": "directory_group_01HQ8ZK3M4N5P6R7S8T9V0W1X2",
10 "directory_id": "directory_01HQ8ZK3M4N5P6R7S8T9V0W1X2",
11 "name": "Engineering",
12 "user": {
13 "id": "directory_user_01HQ8ZK3M4N5P6R7S8T9V0W1X2",
14 "idp_id": "00u1a0ufowBJlzPlk357"
15 }
16 },
17 "created_at": "2026-07-04T11:02:38.114Z"
18}

Replay from a cursor

Same endpoint, different intent. Pass the id you stored as the after cursor and page forwards until the cursor is null. Because ordering is stable, replaying the same range twice produces the same sequence — which only helps if your handler is idempotent.

GET /events?after=:id
1curl "https://api.paycux.com/events?after=event_01HQ8ZK3M4N5P6R7S8T9V0W1X2&limit=100" \
2 -H "Authorization: Bearer sk_example_123456789"
response
1HTTP 200
2{
3 "object": "list",
4 "data": [
5 { "object": "event", "id": "event_01HQ8ZK3M4N5P6R7S8T9V0W1X3", "event": "dsync.user.updated", "created_at": "2026-07-04T11:03:02.008Z" },
6 { "object": "event", "id": "event_01HQ8ZK3M4N5P6R7S8T9V0W1X4", "event": "dsync.user.deleted", "created_at": "2026-07-04T11:05:44.910Z" }
7 ],
8 "list_metadata": { "before": null, "after": "event_01HQ8ZK3M4N5P6R7S8T9V0W1X4" }
9}

Errors

A cursor older than the 30-day retention window returns 422 rather than silently starting from the beginning, because a silent restart would replay a month of events into a handler expecting a handful. If that happens, reconcile from the resource endpoints instead.

Pagination

Cursor pagination with limit, before and after — and here the cursor is doing real work, not just paging a list. Ordering by created_at is stable, so the same replay produces the same sequence every time.

catch-up.ts
1// Kesintiden sonra kaldigin yerden devam et.
2let after = await store.getLastEventId();
3
4for (;;) {
5 const page = await paycux.events.listEvents({ after, limit: 100 });
6 if (page.data.length === 0) break;
7
8 for (const event of page.data) {
9 await handle(event);
10 await store.setLastEventId(event.id);
11 }
12
13 after = page.listMetadata.after;
14 if (!after) break;
15}