Events & Replay
Webhook delivery ledger for listing events, filtering by status, and replaying DEAD or FAILED deliveries.
Webhook deliveries in Pientegra are backed by a durable outbox row. Partners use two endpoints to read and recover from this outbox:
GET /external/eventslists all events in the caller's tenant and site scope.POST /external/events/:id/replayrequeues an event inDEAD,FAILED, orDELIVEREDstate.
This is analogous to Stripe events.list plus events.resend. It is the official
way to recover events after webhook handler downtime without opening a manual ops
ticket.
List events
GET /api/v1/external/eventscurl "$PIENTEGRA_API_BASE/external/events?createdFrom=2026-04-27T00:00:00Z&deliveryStatus=DEAD&limit=100" \
-H "Authorization: Bearer $PIENTEGRA_API_KEY"Query parameters
| Parameter | Type | Description |
|---|---|---|
createdFrom | ISO 8601 | Lower bound for event creation time, inclusive. |
createdTo | ISO 8601 | Upper bound, exclusive. |
eventType | comma-separated | Example: intent.approved,intent.rejected. |
deliveryStatus | comma-separated | PENDING, DELIVERED, FAILED, DEAD. |
cursor | opaque | Returned from the previous response's nextCursor. |
limit | int | 1..100, default 50. |
Response shape
{
"data": [
{
"eventId": "019dd459-43c1-711c-9e38-76a3182f4185",
"eventType": "intent.approved",
"occurredAt": "2026-04-28T13:00:00.000Z",
"deliveryStatus": "DELIVERED",
"attempts": 1,
"lastResponseStatus": 200,
"lastError": null,
"deliveredAt": "2026-04-28T13:00:00.412Z",
"payload": {
"intent": {
"id": "01927b3c-...",
"referenceNo": "5MDLZBFDY7",
"externalUserId": "user-12345",
"requestedAmount": "100000",
"amount": "100000",
"currency": "TRY",
"status": "APPROVED"
}
}
}
],
"nextCursor": null
}The payload field stores the same envelope your webhook handler saw in the HTTP
body, excluding eventId, eventType, and occurredAt, which are exposed as
separate fields. Replay does not generate a new body; the partner receives the
same signed body again.
Replay an event
POST /api/v1/external/events/:id/replayRequeues an event in DEAD or FAILED state. It is dispatched with the same
eventId and the same Pientegra-Event-Id header, so the partner handler should
treat it as a duplicate event and process it idempotently.
curl -X POST "$PIENTEGRA_API_BASE/external/events/019dd459-43c1-711c-9e38-76a3182f4185/replay" \
-H "Authorization: Bearer $PIENTEGRA_API_KEY"{
"eventId": "019dd459-43c1-711c-9e38-76a3182f4185",
"deliveryStatus": "PENDING"
}Replay eligibility
| Current status | Can it be replayed? |
|---|---|
PENDING | No. It is already queued or in-flight, and returns 409. |
DELIVERED | Yes. The partner processes it as a duplicate. |
FAILED | Yes. It forces another attempt before the next scheduled retry. |
DEAD | Yes. This is the main recovery path after terminal failure. |
After replay, the attempt counter is reset to 0 and the event starts a fresh
8-attempt cycle.
Recovery playbook
- Detect webhook handler downtime.
- List DEAD events with
GET /external/events?deliveryStatus=DEAD&createdFrom=<incident_start>. - Call
POST /external/events/:id/replayfor each event. - Because the handler sees the same
Pientegra-Event-Id, correct deduplication preserves idempotency and side effects run only once.
Events are durable. Partners can request historical replay at any time today; a future version may limit replay to events created within a recent retention window, such as the last 30 days.