API REFERENCE
Beta
Query Deliveries
Return a page of delivery summaries for one endpoint, optionally filtered by status and by a creation time range.
POST /v1/webhooks/deliveries/{endpointId}/query
curl -X POST https://api.pointservices.com/riskinsight-services-ws/resources/v1/webhooks/deliveries/4711/query \
-H "Authorization: Bearer your_access_token_here" \
-H "Content-Type: application/json" \
-d '{
"status": "FAILED",
"limit": 2
}'
The filter and the pagination cursor travel in the request body rather than in the query string, so this listing is a POST and creates nothing. The body is optional: send {}, or no body at all, to request the first page with no filter.
Header Properties
| Property | Value | Required? |
|---|---|---|
| Authorization | Bearer your_access_token_here | true |
| Content-Type | application/json | false |
Path Parameters
| Property | Description | Type | Required |
|---|---|---|---|
| endpointId | The owning endpoint’s id. Deliveries are always addressed through their endpoint, because that is where ownership and authorization live. | number | yes |
Request Data Properties
Every property is optional, and since and until accept an explicit null as well as being omitted.
| Property | Description | Type | Default |
|---|---|---|---|
| marker | The pagination cursor: pass back the nextMarker from a previous response. Exclusive, so the delivery it names is not sent again, and paging proceeds toward newer deliveries. Must parse as a positive integer. | string | |
| limit | The requested page size. Valid range is 1 to 100. A value outside that range is neither an error nor clamped: it is replaced outright by the default of 100. | number | 100 |
| status | Restricts the result to one delivery status. Omit to return every status. | string | |
| since | The start of the window, as an RFC 3339 date-time. Inclusive. Omit, or send an explicit null, to start from the oldest delivery that still exists. | string | |
| until | The end of the window, as an RFC 3339 date-time. Exclusive, so consecutive windows can share a boundary without returning anything twice. Omit, or send an explicit null, to run to now. | string |
limit: 0 and limit: 5000 both yield 100. A request for zero rows returns the largest page, not the smallest and not a 400. If you compute this value, check its range yourself before sending it, and compare the response’s own limit against what you asked for.
The window is over each delivery’s creation time, which is when the event occurred, not when it was last attempted. marker is subordinate to the window rather than an alternative to it: when you send both, the two are combined with AND, so paging walks forward through the window and stops at its end.
curl -X POST https://api.pointservices.com/riskinsight-services-ws/resources/v1/webhooks/deliveries/4711/query \
-H "Authorization: Bearer your_access_token_here" \
-H "Content-Type: application/json" \
-d '{
"since": "2026-08-01T00:00:00Z",
"until": "2026-08-28T00:00:00Z",
"limit": 50
}'
This shape never carries a delivery’s payload or lastResponseBody. Both can be large. Fetch a single delivery to see either.
Responses
200
| Property | Description | Type | Notes |
|---|---|---|---|
| limit | The page size actually applied, after an absent or out-of-range request was replaced with the default of 100. | number | always emitted |
| marker | The cursor you supplied, echoed back verbatim. Absent when none was supplied. | string | conditional |
| status | The status filter you supplied, echoed back. Absent when none was supplied. | string | conditional |
| count | How many deliveries this page carries. | number | always emitted, including 0 |
| nextMarker | The cursor for the next page, present only when this page was full. It is the id of the last delivery actually present in results. | string | conditional |
| results | The page of delivery summaries, never the detail shape. | array | always emitted, including [] |
nextMarker is present here only when the page was full, so a page that is not full is the last one. This differs from the endpoint listing, where a short or empty page can still carry a cursor and you stop only when it is absent. Do not reuse one paging loop for both.
The examples below use a limit of 2 to keep them short, so a full page is two rows. Use a larger page size in practice.
Delivery Summary Properties
| Property | Description | Type | Notes |
|---|---|---|---|
| id | The delivery’s id. This is also the value your delivery listing advertises as nextMarker. | number | |
| eventId | The stable id of the underlying business event, shared by every delivery of it, including replays. Formatted as msg_ followed by a UUID. | string | |
| topic | The published topic this delivery carries. | string | |
| url | The URL this delivery was actually sent to, snapshotted when the delivery was created. Editing the URL between retries does not redirect deliveries already under way. | string | |
| status | The delivery’s lifecycle state. | string | |
| simulated | Whether this delivery came from the event simulation operation rather than from a business event. | boolean | always emitted |
| nextAttemptDateTime | When the next retry is due. Present exactly when status is PENDING or RETRYING, and ABSENT, not null, otherwise — a terminal row promises no further attempt. | string | conditional |
| deliveredDateTime | When the delivery succeeded. ABSENT, not null, while the delivery is undelivered. | string | conditional |
| lastResponseStatus | The HTTP status your server returned on the last attempt. Emitted as an explicit null before any attempt, and when the attempt failed without receiving an HTTP response. | number | always emitted, may be null |
| lastFailureReason | Why the last attempt failed. Emitted as an explicit null on a delivery that has not failed. Text for a person, not a stable code. | string | always emitted, may be null |
Example, a full page. count equals limit, so the page was full and carries a cursor:
{
"limit": 2,
"status": "FAILED",
"count": 2,
"nextMarker": "80514",
"results": [
{
"id": 80512,
"eventId": "msg_9f1c2e4a-6b73-3d81-a0c5-4e2f7b9d1350",
"topic": "workflow.updated",
"url": "https://hooks.example.com/pps/orders",
"status": "FAILED",
"simulated": false,
"lastResponseStatus": 500,
"lastFailureReason": "HTTP 500 from destination"
},
{
"id": 80514,
"eventId": "msg_2d7a4c19-8e05-3f62-b431-7c9a0e5d8f26",
"topic": "workflow.failed",
"url": "https://hooks.example.com/pps/orders",
"status": "FAILED",
"simulated": false,
"lastResponseStatus": null,
"lastFailureReason": "Connection timed out"
}
]
}
Example, the next page. count is less than limit, so this page is not full, it carries no nextMarker, and it is the last one:
{
"limit": 2,
"marker": "80514",
"status": "FAILED",
"count": 1,
"results": [
{
"id": 80519,
"eventId": "msg_0c5a83f7-2b19-4e6d-9a04-7f3e1b8c5d20",
"topic": "workflow.failed",
"url": "https://hooks.example.com/pps/orders",
"status": "FAILED",
"simulated": false,
"lastResponseStatus": 503,
"lastFailureReason": "HTTP 503 from destination"
}
]
}
Example, a windowed query. marker is absent from the response because none was sent, and 80531 here is the row the simulate example created, which is why it carries simulated: true.
{
"limit": 50,
"count": 2,
"results": [
{
"id": 80512,
"eventId": "msg_9f1c2e4a-6b73-3d81-a0c5-4e2f7b9d1350",
"topic": "workflow.updated",
"url": "https://hooks.example.com/pps/orders",
"status": "FAILED",
"simulated": false,
"lastResponseStatus": 500,
"lastFailureReason": "HTTP 500 from destination"
},
{
"id": 80531,
"eventId": "msg_1d4b7e02-5a93-4c68-b7f1-0e2a9c6d5384",
"topic": "workflow.updated",
"url": "https://hooks.example.com/pps/orders",
"status": "DELIVERED",
"simulated": true,
"deliveredDateTime": "2026-08-28T14:31:22Z",
"lastResponseStatus": 200,
"lastFailureReason": null
}
]
}
400
A marker that will not parse as a positive integer.
{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"detail": "`marker` carries `80514x`, which does not parse as a positive integer. Pass back the `nextMarker` value unmodified.",
"instance": "urn:pps:request:b5710e83-9c42-4d06-a7f5-1e3b8c024d97"
}
401
No credential was presented, or the one presented was rejected.
404
No such endpoint, delivery or task is available to this request. This also covers a resource that exists but is not yours, or one you may not act on: the three cases are deliberately indistinguishable so that ids cannot be enumerated, and none of them answers 403.
See Errors.
Get One Delivery
Return one delivery including its signed payload and any captured lastResponseBody. This is the one place either is exposed.
GET /v1/webhooks/deliveries/{endpointId}/{deliveryId}
curl -X GET "https://api.pointservices.com/riskinsight-services-ws/resources/v1/webhooks/deliveries/4711/80512" \
-H "Authorization: Bearer your_access_token_here"
A delivery is addressed through its owning endpoint rather than by delivery id alone. The endpoint in the path is what the request is authorized against, and a delivery id that does not belong to that endpoint is a 404.
Path Parameters
| Property | Description | Type | Required |
|---|---|---|---|
| endpointId | The owning endpoint’s id. | number | yes |
| deliveryId | The delivery’s id. | number | yes |
Responses
200
Everything the summary carries, plus the two large fields exposed only here.
| Property | Description | Type | Notes |
|---|---|---|---|
| payload | The signed envelope actually sent to your server, embedded as a JSON object rather than as a string of JSON. ABSENT, never null, when this delivery has no retained payload. | object | conditional |
| lastResponseBody | The response body your server returned on the last attempt. Emitted as an explicit null for a delivery not yet attempted or whose server returned no body. PPS keeps the first 8 KB and discards the rest, with no marker where the cut falls. Untrusted text: it may contain arbitrary content and may stop mid-token. | string | always emitted, may be null |
The null policy differs by property and is part of the contract. lastResponseStatus, lastFailureReason and lastResponseBody are emitted as explicit null. nextAttemptDateTime, deliveredDateTime and payload are absent when they have no value.
Example:
{
"id": 80512,
"eventId": "msg_9f1c2e4a-6b73-3d81-a0c5-4e2f7b9d1350",
"topic": "workflow.updated",
"url": "https://hooks.example.com/pps/orders",
"status": "FAILED",
"simulated": false,
"lastResponseStatus": 500,
"lastFailureReason": "HTTP 500 from destination",
"lastResponseBody": "{\"error\":\"internal\"}",
"payload": {
"type": "workflow.updated",
"eventId": "msg_9f1c2e4a-6b73-3d81-a0c5-4e2f7b9d1350",
"timestamp": "2026-08-28T14:26:03Z",
"data": {
"workflowId": "wfpop_7b3f1c9e-2a84-4d16-9f05-c3e8a1b47d92",
"runId": "wfrunpop_7b3f1c9e-2a84-4d16-9f05-c3e8a1b47d92",
"correlationId": "9c2e14a7-55d0-4b8e-a731-6f0d2b93e185",
"serviceName": "ADV",
"modelName": "ADV-120",
"status": "ready"
}
}
}
401
No credential was presented, or the one presented was rejected.
404
No such endpoint, delivery or task is available to this request. This also covers a resource that exists but is not yours, or one you may not act on: the three cases are deliberately indistinguishable so that ids cannot be enumerated, and none of them answers 403.
See Errors.
Replay One Delivery
Sign the delivery again and queue it for immediate attempt. This request has no body.
POST /v1/webhooks/deliveries/{endpointId}/{deliveryId}/replay
A 200 from this operation does not mean the replay happened. A delivery in PENDING or RETRYING, or an endpoint whose circuit is OPEN, causes the call to do nothing and still return 200 with the delivery unchanged. There is no 409, no error body, and no field that differs between a replay that was queued and one that was refused.
Predict the refusal rather than detecting it afterwards. Before you replay, read circuitState on the endpoint and status on the delivery: an OPEN circuit, or a delivery already PENDING or RETRYING, means the call will do nothing. That check is reliable, and the after-the-fact one below is not.
curl -X POST https://api.pointservices.com/riskinsight-services-ws/resources/v1/webhooks/deliveries/4711/80512/replay \
-H "Authorization: Bearer your_access_token_here"
The delivery keeps its original eventId, so a consumer deduplicating on that value recognizes the replay as the same business event rather than as a new one. A replay is a redelivery, not a new event.
A delivery is replayable only while its row still exists. Rows are removed once they fall outside the delivery retention window, which is 90 days.
Path Parameters
| Property | Description | Type | Required |
|---|---|---|---|
| endpointId | The owning endpoint’s id. | number | yes |
| deliveryId | The delivery’s id. | number | yes |
Responses
200
The delivery in the full detail shape, reporting its state as it stood BEFORE the replay. status and nextAttemptDateTime hold the values from before, so a delivery that is now PENDING is still reported here as FAILED. Do not drive your state machine from this body.
Example. This is byte-identical to the GET above, because it is the same state:
{
"id": 80512,
"eventId": "msg_9f1c2e4a-6b73-3d81-a0c5-4e2f7b9d1350",
"topic": "workflow.updated",
"url": "https://hooks.example.com/pps/orders",
"status": "FAILED",
"simulated": false,
"lastResponseStatus": 500,
"lastFailureReason": "HTTP 500 from destination",
"lastResponseBody": "{\"error\":\"internal\"}",
"payload": {
"type": "workflow.updated",
"eventId": "msg_9f1c2e4a-6b73-3d81-a0c5-4e2f7b9d1350",
"timestamp": "2026-08-28T14:26:03Z",
"data": {
"workflowId": "wfpop_7b3f1c9e-2a84-4d16-9f05-c3e8a1b47d92",
"runId": "wfrunpop_7b3f1c9e-2a84-4d16-9f05-c3e8a1b47d92",
"correlationId": "9c2e14a7-55d0-4b8e-a731-6f0d2b93e185",
"serviceName": "ADV",
"modelName": "ADV-120",
"status": "ready"
}
}
}
Fetch the delivery again to see the state after the replay.
curl -X GET "https://api.pointservices.com/riskinsight-services-ws/resources/v1/webhooks/deliveries/4711/80512" \
-H "Authorization: Bearer your_access_token_here"
A replay that was actually queued changes three fields together: status becomes PENDING, nextAttemptDateTime is set to the instant of the next attempt, and any deliveredDateTime is cleared.
This after-the-fact check cannot tell a queued replay from a refused one on a delivery that was already PENDING or RETRYING. Being already PENDING is itself one of the two refusal conditions, so the delivery reads as PENDING either way and the check reports success for a replay that did nothing. It is dependable only for a delivery that was not PENDING or RETRYING before you called. Check the delivery’s status and the endpoint’s circuitState before replaying instead.
{
"id": 80512,
"eventId": "msg_9f1c2e4a-6b73-3d81-a0c5-4e2f7b9d1350",
"topic": "workflow.updated",
"url": "https://hooks.example.com/pps/orders",
"status": "PENDING",
"simulated": false,
"nextAttemptDateTime": "2026-08-28T15:12:40Z",
"lastResponseStatus": 500,
"lastFailureReason": "HTTP 500 from destination",
"lastResponseBody": "{\"error\":\"internal\"}",
"payload": {
"type": "workflow.updated",
"eventId": "msg_9f1c2e4a-6b73-3d81-a0c5-4e2f7b9d1350",
"timestamp": "2026-08-28T14:26:03Z",
"data": {
"workflowId": "wfpop_7b3f1c9e-2a84-4d16-9f05-c3e8a1b47d92",
"runId": "wfrunpop_7b3f1c9e-2a84-4d16-9f05-c3e8a1b47d92",
"correlationId": "9c2e14a7-55d0-4b8e-a731-6f0d2b93e185",
"serviceName": "ADV",
"modelName": "ADV-120",
"status": "ready"
}
}
}
401
No credential was presented, or the one presented was rejected.
404
No such endpoint, delivery or task is available to this request. This also covers a resource that exists but is not yours, or one you may not act on: the three cases are deliberately indistinguishable so that ids cannot be enumerated, and none of them answers 403.
See Errors. This operation never returns 409.
Replay In Bulk
Select deliveries over a date range, narrowed by an optional status, and queue them. The call is asynchronous: it returns 202 with a task handle as soon as the task is accepted.
POST /v1/webhooks/deliveries/{endpointId}/replay
curl -X POST https://api.pointservices.com/riskinsight-services-ws/resources/v1/webhooks/deliveries/4711/replay \
-H "Authorization: Bearer your_access_token_here" \
-H "Content-Type: application/json" \
-d '{
"since": "2026-08-01T00:00:00Z",
"until": "2026-08-28T00:00:00Z",
"status": "FAILED"
}'
Header Properties
| Property | Value | Required? |
|---|---|---|
| Authorization | Bearer your_access_token_here | true |
| Content-Type | application/json | true |
Request Data Properties
| Property | Description | Type | Required |
|---|---|---|---|
| since | The start of the replay window, as an RFC 3339 date-time. Inclusive. Only an absolute instant is accepted; a relative window such as “the last 30 days” is not. | string | yes |
| until | The end of the replay window. Exclusive, so consecutive windows can share a boundary without replaying anything twice. Omit, or send an explicit null, to replay everything from since up to now. | string | no |
| status | Restricts the replay to one delivery status. Omit to select the two stalled states, FAILED plus PARKED. | string | no |
The window is the bound on how much one replay queues. There is no per-request ceiling on how many deliveries one replay may queue: narrow the window if you want to queue fewer. The window is over each delivery’s creation time, which is when the event occurred, not when it was last attempted.
Four statuses can be replayed: FAILED, PARKED, DELIVERED and CANCELLED. Omitting status selects FAILED plus PARKED, which is what recovers a disabled endpoint: enable it again, then run a bulk replay with no status.
A delivery PPS made successfully is DELIVERED however your service handled it afterwards, so the default selection does not reach events your own consumer dropped. If your consumer acknowledged deliveries with a 2xx during an outage and then lost them, name status: "DELIVERED" and the window your consumer was broken in. This re-sends events your service has already seen, so your handler must tolerate duplicates.
curl -X POST https://api.pointservices.com/riskinsight-services-ws/resources/v1/webhooks/deliveries/4711/replay \
-H "Authorization: Bearer your_access_token_here" \
-H "Content-Type: application/json" \
-d '{
"since": "2026-08-01T00:00:00Z",
"until": "2026-08-28T00:00:00Z",
"status": "DELIVERED"
}'
Replayed deliveries are spread two seconds apart rather than queued all at once, so a replay of 100 deliveries drains over roughly three and a half minutes. Do not read a slow drain as a failed replay.
Concurrent bulk replays against the same endpoint are bounded, not prevented. Nothing serializes two replays whose windows overlap, so a delivery selected by both can be reset and enqueued twice. If a replay seems slow, poll the task rather than issuing a second call, which doubles the traffic.
Responses
202
The replay task was accepted. The deliveries are queued after this response returns, so poll the task for progress rather than reading this body as a result.
| Property | Description | Type | Notes |
|---|---|---|---|
| taskId | The task’s handle, formatted as wrt_ followed by an identifier. Treat it as opaque. | string | always emitted |
| endpointId | The endpoint whose deliveries this task is replaying. | number | always emitted |
| status | PENDING, RUNNING, COMPLETED or FAILED. | string | always emitted |
| since | The resolved start of the window, echoed back. Inclusive. | string | always emitted, because it is required on the request |
| until | The resolved end of the window, echoed back. Exclusive. | string | optional; emitted as null when the request named none |
| replayed | How many deliveries the task has queued so far. It grows while the task is RUNNING and is final once it is COMPLETED. | number | always emitted |
| stoppedReason | Why the task stopped before covering the whole window. Present only on a FAILED task. Text for a person, not a stable code. | string | conditional |
Example:
{
"taskId": "wrt_60184",
"endpointId": 4711,
"status": "PENDING",
"since": "2026-08-01T00:00:00Z",
"until": "2026-08-28T00:00:00Z",
"replayed": 0
}
400
Naming PENDING or RETRYING is rejected, because those rows still have a live delivery task driving them and replaying one would race it.
{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"detail": "`status` carries `RETRYING`, which cannot be replayed. Replayable statuses are FAILED, PARKED, DELIVERED and CANCELLED.",
"instance": "urn:pps:request:47c1e0b9-3f86-4a25-9d70-8b2e5c1a6f34"
}
An absent body is equivalent to {}, which is rejected with a 400 naming since.
{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"detail": "`since` is required on a bulk replay and was not supplied. The window is the bound on how much one replay queues.",
"instance": "urn:pps:request:e28b647a-5d13-4f90-bc36-0a97e4d152b8"
}
409
A replay into an endpoint whose circuit is currently OPEN is refused outright, never silently accepted. A task that completes with replayed: 0 means “nothing matched the filter”, never “the endpoint refused”.
{
"type": "https://pointservices.com/problems/conflict",
"title": "The resource's current state refuses this request",
"status": 409,
"detail": "Endpoint 4711 has an OPEN circuit, so a bulk replay is refused until it closes.",
"instance": "urn:pps:request:5c2b9e47-0a38-4f15-9d6e-2b7c4a1f8035"
}
401
No credential was presented, or the one presented was rejected.
404
No such endpoint, delivery or task is available to this request. This also covers a resource that exists but is not yours, or one you may not act on: the three cases are deliberately indistinguishable so that ids cannot be enumerated, and none of them answers 403.
See Errors.
Get A Bulk Replay Task
Report the state of one bulk replay task: whether it is still running, how many deliveries it has queued so far, and the window it resolved.
GET /v1/webhooks/deliveries/{endpointId}/replay/{taskId}
curl -X GET "https://api.pointservices.com/riskinsight-services-ws/resources/v1/webhooks/deliveries/4711/replay/wrt_60184" \
-H "Authorization: Bearer your_access_token_here"
Poll until the task is no longer PENDING or RUNNING. replayed grows while RUNNING and is final at COMPLETED. stoppedReason is present only on a FAILED task; branch on status, never on that string.
The task is scoped to the endpoint in the path, so a task id belonging to a different endpoint is a 404. Task status is retained for 30 days, after which the task id reads as a 404. Record the outcome you care about rather than relying on the task as a durable record.
Path Parameters
| Property | Description | Type | Required |
|---|---|---|---|
| endpointId | The owning endpoint’s id. | number | yes |
| taskId | The handle a bulk replay returned. Treat it as opaque and pass it back unmodified. | string | yes |
Responses
200
The same shape the bulk replay returns.
Example, while the task is running:
{
"taskId": "wrt_60184",
"endpointId": 4711,
"status": "RUNNING",
"since": "2026-08-01T00:00:00Z",
"until": "2026-08-28T00:00:00Z",
"replayed": 1420
}
Example, once it has finished the whole window:
{
"taskId": "wrt_60184",
"endpointId": 4711,
"status": "COMPLETED",
"since": "2026-08-01T00:00:00Z",
"until": "2026-08-28T00:00:00Z",
"replayed": 1883
}
401
No credential was presented, or the one presented was rejected.
404
No such endpoint, delivery or task is available to this request. This also covers a resource that exists but is not yours, or one you may not act on: the three cases are deliberately indistinguishable so that ids cannot be enumerated, and none of them answers 403.
See Errors.
Delivery Statuses
| Status | Meaning | Replayable? |
|---|---|---|
| PENDING | Queued and not yet attempted. | No. A bulk replay naming it is a 400; a single replay returns 200 and does nothing. |
| RETRYING | Has failed at least once and has a further attempt scheduled. | No, on the same terms as PENDING. |
| DELIVERED | Succeeded. PPS made the delivery, however your service handled it afterwards. | Yes. Name it explicitly to recover events your own consumer lost. |
| FAILED | Terminal. Either it exhausted its ten attempts, or your endpoint answered 410 Gone or another non-retryable 4xx, which ends a delivery on the first response. | Yes. Selected by the default when status is omitted. |
| PARKED | Held aside because its endpoint was disabled or its destination domain is no longer authorized. Not discarded, and no attempt is spent. | Yes. Selected by the default when status is omitted. |
| CANCELLED | A terminal state carried by some historical rows. No delivery enters it today; it remains in the vocabulary so those rows stay filterable. | Yes, when named explicitly. |
Matching is exact and sensitive to case when used as a filter.
Troubleshooting walks through finding a missing delivery and replaying it.