Events
An event is an immutable record of something that happened in your workspace — a checkout completed, an invoice paid, a subscription canceled. Events are the same payloads Plugipay delivers via webhooks; the events log is the read-side API for the same data, suitable for replay, debugging, and "I missed a webhook, what happened in the last hour?" recoveries. This page covers the plugipay.events namespace; for the underlying HTTP surface see API: Events, and for the typed event shapes see Webhooks.
Namespace
plugipay.events — every method on this namespace:
plugipay.events.list(params?)
plugipay.events.get(id)
Events are emitted automatically; you don't create them. The live delivery channel is webhooks — use the events API for replay and forensics.
Methods
events.list
Signature. plugipay.events.list(params?): Promise<{ data: EventRecord[]; cursor: string | null; hasMore: boolean }>
Cursor-paginated list. Filters:
type— an event type, e.g.'plugipay.checkout_session.completed.v1'. Exact match; no wildcards.occurredAfter,occurredBefore— ISO-8601 strings.order—'asc'(oldest first — the right choice for replay) or'desc'(newest first — default, the right choice for dashboards).limit,cursor.
import { PlugipayClient } from '@forjio/plugipay-node';
const plugipay = new PlugipayClient({
keyId: process.env.PLUGIPAY_KEY_ID!,
secret: process.env.PLUGIPAY_SECRET!,
});
// All completed checkout sessions since yesterday:
const since = new Date(Date.now() - 86_400_000).toISOString();
const { data } = await plugipay.events.list({
type: 'plugipay.checkout_session.completed.v1',
occurredAfter: since,
order: 'asc',
limit: 100,
});
for (const ev of data) {
console.log(ev.id, ev.occurredAt, ev.type);
}
events.get
Signature. plugipay.events.get(id): Promise<EventRecord>
Fetch one event by its evt_* ID. Useful when a webhook receiver logs the event ID and you want to re-inspect the full payload.
const ev = await plugipay.events.get('evt_01HX...');
console.log(ev.type, ev.data);
Types
interface EventRecord {
id: string; // 'evt_...'
type: string; // e.g. 'plugipay.invoice.paid.v1'
accountId: string;
occurredAt: string; // ISO-8601
data: unknown; // shape depends on type — narrow via the WebhookEvent union
}
The data field is typed unknown here because it varies per type. For the typed shapes (which mirror webhook payloads exactly), see the WebhookEvent union in Webhooks or API: Events. The narrowing pattern:
import { WebhookEvent } from '@forjio/plugipay-node';
function narrow(ev: EventRecord): WebhookEvent | null {
return ev as unknown as WebhookEvent; // shape-compatible at runtime
}
const ev = await plugipay.events.get('evt_01HX...');
const typed = narrow(ev);
if (typed?.type === 'plugipay.checkout_session.completed.v1') {
console.log(typed.data.object.hostedUrl);
}
Common patterns
Replay missed webhooks
The most common reason to use the events API: your webhook receiver was down for an hour, and you need to catch up. Walk the events log in asc order from the last successfully-processed event:
async function replayMissed(sinceEventId: string) {
// Get the timestamp of the last-processed event:
const since = (await plugipay.events.get(sinceEventId)).occurredAt;
let cursor: string | undefined;
while (true) {
const page = await plugipay.events.list({
occurredAfter: since,
order: 'asc',
limit: 100,
cursor,
});
for (const ev of page.data) {
await myWebhookHandler(ev as unknown as WebhookEvent);
}
if (!page.hasMore) break;
cursor = page.cursor!;
}
}
Make your webhook handler idempotent. Replay implies you may re-process events you've already seen. Key your handler on
event.idand deduplicate on first sight — the same advice that applies to live webhook delivery applies double for replay.
Build an event-log dashboard
Newest-first scrolling, paginated:
async function eventPage(cursor?: string) {
return plugipay.events.list({ order: 'desc', limit: 50, cursor });
}
// In your dashboard:
const first = await eventPage();
const next = first.hasMore ? await eventPage(first.cursor!) : null;
Filter for specific event types
For a "subscription churn" report, list canceled and expired events:
async function* churnEvents(from: string, to: string) {
for (const type of [
'plugipay.subscription.canceled.v1',
'plugipay.checkout_session.expired.v1',
]) {
let cursor: string | undefined;
while (true) {
const page = await plugipay.events.list({
type,
occurredAfter: from,
occurredBefore: to,
order: 'asc',
limit: 100,
cursor,
});
for (const ev of page.data) yield ev;
if (!page.hasMore) break;
cursor = page.cursor!;
}
}
}
Diff event vs current state
A common debugging case: "the event said the subscription was active, but the current object is canceled — when did it change?" Compare the event's snapshot with a current get:
async function subscriptionDelta(eventId: string) {
const ev = (await plugipay.events.get(eventId)) as unknown as WebhookEvent;
if (ev.type.startsWith('plugipay.subscription.')) {
const eventSnapshot = ev.data.object;
const current = await plugipay.subscriptions.get(eventSnapshot.id);
return { wasStatus: eventSnapshot.status, isStatus: current.status };
}
}
Build a "what happened today" digest
Used for daily ops email or Slack digests:
async function todayDigest() {
const start = new Date();
start.setUTCHours(0, 0, 0, 0);
const buckets: Record<string, number> = {};
let cursor: string | undefined;
while (true) {
const page = await plugipay.events.list({
occurredAfter: start.toISOString(),
order: 'asc',
limit: 200,
cursor,
});
for (const ev of page.data) {
buckets[ev.type] = (buckets[ev.type] ?? 0) + 1;
}
if (!page.hasMore) break;
cursor = page.cursor!;
}
return buckets;
}
Errors
| Code | Status | Cause |
|---|---|---|
validation_error |
400 | Bad ISO-8601 in occurredAfter / occurredBefore, unknown type. |
not_found |
404 | Event ID doesn't exist. |
forbidden |
403 | Key lacks plugipay:event:read scope. |
Next
- Webhooks — live delivery of the same events.
- Webhook endpoints — manage the destinations.
- API: Events — HTTP reference.