Events

Events are a queryable archive of every state change in your Plugipay workspace. The same payment.succeeded, refund.created, subscription.updated notifications that fire on your webhook endpoints are also written to this archive — pull them at any time with the standard HMAC-signed API.

If webhooks are the push channel, events are the pull channel. Same payload shape, same id, same type.

This page covers querying events. For the full catalog of event types (what fires when), how delivery and retries work, and how to verify webhook signatures, see Webhooks.

When to use this resource

Three common reasons to reach for events:

  • Backfill missed webhooks. Your endpoint was down and Plugipay gave up retrying after 72 hours. Pull the time window and replay locally.
  • Audit history. Reconstruct what happened to a payment, subscription, or customer over time — for support, dispute response, or compliance.
  • Sync state at startup. A worker boots fresh; page through events since=<last_processed_at> to catch up before going live.

For real-time delivery, prefer webhooks. Polling this endpoint as a primary integration burns rate limit.

Endpoints

Method Path What Scope
GET /v1/events/:id Retrieve by ID plugipay:event:read
GET /v1/events List with filters plugipay:event:read
POST /v1/events/replay Re-emit to webhook endpoints plugipay:event:write
POST /v1/events/trigger Synthetic event (test mode only) plugipay:event:trigger

Retrieve an event

GET /v1/events/:id

Returns the event with the given ID. Events are scoped to your workspace — you can't read another workspace's events.

Response

200 OK with the event object in data.

Errors

Status Code When
404 not_found Event doesn't exist or belongs to a different workspace.

Samples

// Node
const event = await plugipay.events.retrieve('evt_01HX...');
# Python
event = plugipay.events.retrieve("evt_01HX...")
// Go
ev, err := client.Events.Retrieve(ctx, "evt_01HX...")
# curl — plugipay_curl() defined in /docs/api/authentication
plugipay_curl GET '/v1/events/evt_01HXxxxxxxxxxxxxxxxxxxxxxx'

List events

GET /v1/events

Returns a paginated list of events for the workspace, most recent first.

Query parameters

Name Type Notes
type string Exact match on event type, e.g. payment.succeeded. Omit for all types.
occurredAfter ISO 8601 or epoch Only events with occurredAt > this. Synonyms: since.
occurredBefore ISO 8601 or epoch Only events with occurredAt < this. Synonyms: until.
objectId string Only events whose data.id (or data.aggregateId) matches. Useful for "all events for this payment".
workspaceId string Platform-admin keys only — constrain to a single merchant. Ignored for regular keys (always scoped to the key's workspace).
limit int Page size, 1–100. Default 20.
cursor string Pagination cursor from the previous response. See Pagination.
order asc | desc Default desc (most recent first).

Test-mode keys see both test and live events from their workspace; live-mode keys see only live events. (The mode is tagged on each event when it's emitted.)

Response

200 OK with an array of event objects in data and a standard cursor in meta.page. See Pagination for the envelope.

Samples

// Node — auto-pagination
for await (const evt of plugipay.events.list({
  type: 'payment.succeeded',
  occurredAfter: '2026-05-12T00:00:00Z',
})) {
  await handle(evt);
}
# Python — manual cursor loop
cursor = None
while True:
    page = plugipay.events.list(
        type="payment.succeeded",
        occurred_after="2026-05-12T00:00:00Z",
        cursor=cursor,
        limit=100,
    )
    for evt in page.data:
        handle(evt)
    if not page.has_more:
        break
    cursor = page.next_cursor
// Go
iter := client.Events.List(ctx, &plugipay.EventListParams{
    Type:          plugipay.String("payment.succeeded"),
    OccurredAfter: plugipay.String("2026-05-12T00:00:00Z"),
})
for iter.Next() { handle(iter.Event()) }
# curl
plugipay_curl GET '/v1/events?type=payment.succeeded&occurredAfter=2026-05-12T00:00:00Z'

Replay events

POST /v1/events/replay

Re-emits one or more events to all subscribed webhook endpoints. Use this when your service was down and you want Plugipay to push the events again rather than polling.

Each replayed event is a new event with its own id (and metadata.replayOf pointing at the original). If your handler dedupes on event.id, the replay will look new — which is what you want, since the original was missed.

Idempotency-Key is required — see Idempotency.

Body

Field Type Notes
eventIds string[] 1–10,000 event IDs to replay. Each must belong to your workspace.

Response

201 Created:

{
  "data": {
    "count": 3,
    "eventIds": ["evt_01HX...", "evt_01HX...", "evt_01HX..."]
  },
  "error": null,
  "meta": { "requestId": "...", "timestamp": "..." }
}

The returned eventIds are the new event IDs — the ones that will be sent to your webhook endpoints.

Errors

Status Code When
400 validation_error Empty array, more than 10,000 IDs, or malformed IDs.
404 not_found One or more event IDs don't exist in your workspace (whole request fails).

Trigger a synthetic event (test mode only)

POST /v1/events/trigger

Creates a synthetic event for local development — exercise your webhook handlers without going through a full checkout/refund flow. Live-mode keys get 403 forbidden_in_live_mode. Idempotency-Key is required.

Body

Field Type Notes
type string Any event type, e.g. payment.succeeded. Not validated against the canonical catalog.
aggregateId string ID of the object the event is about. Stored under data.aggregateId.
data object Arbitrary payload, merged with aggregateId. Defaults to {}.

201 Created returns the event object and dispatches it to your test-mode webhook endpoints normally.

The event object

{
  "id": "evt_01HXxxxxxxxxxxxxxxxxxxxxxx",
  "type": "payment.succeeded",
  "occurredAt": "2026-05-12T10:42:00.123Z",
  "createdAt": "2026-05-12T10:42:00.150Z",
  "publishedAt": "2026-05-12T10:42:00.480Z",
  "mode": "live",
  "data": { "id": "pay_01HX...", "amount": 250000, "currency": "IDR", "status": "succeeded", "...": "..." },
  "metadata": { "source": "ingress.xendit", "requestId": "req_01H..." }
}
Field Type Notes
id string Stable event ID, evt_<ulid>. Same ID you receive in the webhook delivery.
type string <resource>.<action> — see the event catalog.
occurredAt ISO 8601 When the underlying event happened. Use this for ordering.
createdAt ISO 8601 When the event row was written to the outbox.
publishedAt ISO 8601 | null When delivery to webhook endpoints was first attempted. null if not yet published or no subscriber exists.
mode live | test | null Which environment produced the event. null for legacy rows without mode tagging.
data object The full resource snapshot, same shape as the API returns for that resource.
metadata object Plugipay-internal context: emitter source, originating request ID, replay markers, etc. Not your metadata field on the underlying resource — that lives inside data.

The data shape matches the webhook payload exactly — the same parser works for both.

Pagination notes

Busy workspaces accumulate events fast — thousands per hour is normal. Three things to keep in mind:

  • Always page with the cursor. Offsets would skip or duplicate when new events arrive between pages.
  • Pull narrow time windows. Prefer occurredAfter + occurredBefore over re-reading the world from the start.
  • Order is desc by default. For chronological backfill (process oldest first), pass order=asc.

Retention

Events are retained for at least 90 days from createdAt. In practice most workspaces have far longer history — we keep events as long as it's economical and prune the very oldest. Don't rely on events older than 90 days being present.

For durable long-term archival (tax, audit, analytics), sync events into your own data warehouse on an ongoing basis. The list endpoint ordered by createdAt is the canonical extraction path.

90 days is a floor, but not a contract. If you need a 7-year audit trail for compliance, your warehouse owns that — not us.

Rate limits

List and retrieve are in the read class; replay is in mutating_heavy (replays produce real webhook deliveries). See Rate limits.

Next

Plugipay — Payments that don't tax your success