API REFERENCE
Beta
Webhooks API
Register and configure webhook delivery endpoints, set their signing secrets, enable and disable them, and inspect or replay individual deliveries.
If you are integrating for the first time, start with the Webhooks Developer Guide.
Every path below is relative to the base URL:
https://api.pointservices.com/riskinsight-services-ws/resources
| Operation | Method and path | Reference |
|---|---|---|
| Register an endpoint | POST /v1/webhooks/endpoints | Endpoints |
| List endpoints | GET /v1/webhooks/endpoints | Endpoints |
| Get one endpoint | GET /v1/webhooks/endpoints/{id} | Endpoints |
| Edit an endpoint | POST /v1/webhooks/endpoints/{id} | Endpoints |
| Query deliveries | POST /v1/webhooks/deliveries/{endpointId}/query | Deliveries |
| Get one delivery | GET /v1/webhooks/deliveries/{endpointId}/{deliveryId} | Deliveries |
| Replay one delivery | POST /v1/webhooks/deliveries/{endpointId}/{deliveryId}/replay | Deliveries |
| Replay in bulk | POST /v1/webhooks/deliveries/{endpointId}/replay | Deliveries |
| Get a bulk replay task | GET /v1/webhooks/deliveries/{endpointId}/replay/{taskId} | Deliveries |
| Send a test delivery | POST /v1/webhooks/endpoints/{id}/test | Events |
| Simulate an event | POST /v1/webhooks/events/simulate | Events |
Getting Started walks through registering an endpoint and receiving a first verified delivery, including where your org unit GUID comes from — you obtain it from your PPS account manager before any call here will succeed.
Authentication
Every request must be authenticated with a bearer token in the standard Authorization header, written as Authorization: Bearer <token>. That is the only supported mechanism. Authentication is checked before the request body is read, so an unauthenticated request never reveals whether its body would have been accepted.
The scheme name is matched without regard to case, but the header must carry the scheme and the token separated by a single space. A bare token with no Bearer prefix is rejected.
Obtaining An Access Token — sign in with the credentials PPS issued you. An access token expires one hour after it is issued, so refresh it. The refresh token expires too, on its own schedule, so an unattended integration must be able to sign in again on its own.
Conventions
Every timestamp in this API, sent or received, is an RFC 3339 instant in UTC with a trailing Z, for example "2026-08-28T14:26:03Z". Fractional seconds are emitted only when they are not zero, so do not assume a fixed field width, and parse with a library rather than by slicing the string.
Unknown JSON keys in a request body are ignored rather than rejected, so a client written against a later version of this API can safely call an earlier deployment. The cost is that a misspelled property name is silently dropped: compare what comes back against what you sent.
Errors
Every error response in this API is an RFC 9457 problem document, served as application/problem+json. One shape covers every status, so you parse errors the same way whatever went wrong.
{
"type": "https://pointservices.com/problems/invalid-token",
"title": "The supplied credential was rejected",
"status": 401,
"detail": "The bearer token in the `Authorization` header was rejected. Obtain a fresh token rather than inspecting the one you sent.",
"instance": "urn:pps:request:4b8c2d90-7f31-4a6e-95b2-0c3e8a7d1f45"
}
Problem Properties
| Property | Description | Type | Required |
|---|---|---|---|
| type | An absolute URI identifying the problem type. This is the stable value your program matches on. about:blank means the status code alone describes the problem. | string | yes |
| title | A short summary of the problem type, written for a person. Stable for a given type, but written for a reader rather than for matching. | string | yes |
| status | The HTTP status code, repeated in the body so a logged problem document stands on its own. | number | yes |
| detail | An explanation of this occurrence, naming the offending field where one is known. Written for your logs, not a stable code. | string | no |
| instance | A URI identifying this occurrence, written as urn:pps:request:<id>. Quote it when you raise a support ticket. | string | no |
Branch on type where there is one, and compare it as a string — a type URI identifies a problem, it is not a document you fetch. Never branch on the text of title or detail: both are written for a person to read and neither is a stable code.
Your handler must tolerate about:blank: every 400 carries it, as does a 401 with no credential and every 5xx. A 401 with a rejected token, and every 403, 404, 409 and 429, carries a dedicated type. Branch on type where there is one, fall back to the status otherwise — a client that switches on type alone cannot tell two different 400s apart — and keep a default for anything you do not recognize.
Problem types
This is the complete set of types this API emits.
| type | Status | What it means | What to do |
|---|---|---|---|
about:blank | 400 | The request body was malformed, carried a value of the wrong type, or carried a value outside this API’s vocabulary. 400s are not typed, so detail is the only thing that distinguishes them. | Read detail and fix the request. Do not pattern-match its text. |
https://iana.org/assignments/http-problem-types#digest-mismatched-values | 400 | The body did not match the digest sent with it. | Send the request again. This is IANA’s registered type rather than a PPS one. |
https://pointservices.com/problems/invalid-token | 401 | A credential was presented and rejected. | Obtain a fresh token. |
about:blank | 400, 401 with no credential, and any status with no dedicated type of its own, including every 5xx | No credential was presented, or the status carries no dedicated type. | For a 401, send an Authorization header. For a 5xx, retry. |
https://pointservices.com/problems/not-authorized | 403 | You are authenticated, but you may not act on the org unit you named in the request. | Name an org unit you hold rights over, or ask your PPS account manager to grant them. |
https://pointservices.com/problems/not-found | 404 | The resource is missing, out of your tenancy, or one you may not act on. | Check the id you sent. |
https://pointservices.com/problems/conflict | 409 | The resource’s current state refuses the request. | Change the state first, then retry. |
https://pointservices.com/problems/concurrent-modification | 409 | The resource changed under you and a retry is expected to succeed. | Read the resource again and retry. |
https://pointservices.com/problems/too-many-requests | 429 | The endpoint’s delivery rate budget for the current window is spent. | Try again in about a second. |
Status by status
400
The request body was malformed, carried a value of the wrong type, or carried a value outside this API’s vocabulary: an unknown topic, an unrecognized delivery status, an implausible contact address, or a marker that is not a delivery id. Every 400 carries about:blank, so detail is the only thing that tells two of them apart.
Unknown keys are not an error. They are dropped, so a client written against a later version keeps working against an earlier deployment.
{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"detail": "`acceptedEventTypes[0].eventTypes[1]` carries `workflow.done`, which is not a published topic.",
"instance": "urn:pps:request:1e5f9a24-3b70-4c82-a6d1-9f4b7e0c3852"
}
401
The request carried no valid authentication. Two cases produce this status, and type tells them apart. invalid-token means a credential was presented and rejected.
{
"type": "https://pointservices.com/problems/invalid-token",
"title": "The supplied credential was rejected",
"status": 401,
"detail": "The bearer token in the `Authorization` header was rejected. Obtain a fresh token rather than inspecting the one you sent.",
"instance": "urn:pps:request:4b8c2d90-7f31-4a6e-95b2-0c3e8a7d1f45"
}
invalid-token does not say why the credential was rejected. PPS cannot distinguish an expired token from a malformed one from a badly signed one. Treat every one the same way: obtain a fresh token rather than inspecting the one you sent.
about:blank means no credential was presented at all.
{
"type": "about:blank",
"title": "Unauthorized",
"status": 401,
"detail": "No credential was presented. Send an `Authorization: Bearer <token>` header.",
"instance": "urn:pps:request:a71f3c28-5d06-4b94-8e2a-1f5c7d0b3946"
}
403
You named an org unit you hold no rights over. Only the operations that take an org unit from your request body — registering an endpoint, moving one to a different org unit, and simulating an event — can return this. Naming an endpoint id that is not yours is a 404, not a 403, so ids cannot be probed by watching the status change.
{
"type": "https://pointservices.com/problems/not-authorized",
"title": "Not authorized",
"status": 403,
"detail": "The authenticated identity is not permitted to perform this action.",
"instance": "urn:pps:request:2f7e4c83-b019-4d56-8a3f-6e1d0b9c5a27"
}
404
The resource you named is missing, out of your tenancy, or one you may not act on. The three are deliberately indistinguishable, so endpoint ids cannot be enumerated: an endpoint that exists but is not yours answers 404, never 403.
{
"type": "https://pointservices.com/problems/not-found",
"title": "No such resource",
"status": 404,
"detail": "No endpoint 4711 is available to this request.",
"instance": "urn:pps:request:8d3a1f56-6c94-4e20-b7f8-0a5e9c2d4b73"
}
409
Two different types share this status, and they call for different handling.
conflict means the resource’s current state refuses the request. Two conditions produce it: a test to an endpoint whose destination domain is no longer authorized, and a bulk replay into an endpoint whose circuit is OPEN. Both need the endpoint’s state to change first.
{
"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"
}
concurrent-modification means the resource changed under you and your write was not applied. It is worth retrying as it stands: read the resource again, then send the edit again.
{
"type": "https://pointservices.com/problems/concurrent-modification",
"title": "The resource was modified concurrently",
"status": 409,
"detail": "Endpoint 4711 changed while your write was in flight, so nothing was applied. Read it again and retry.",
"instance": "urn:pps:request:3a6f80d2-1e74-4b93-a5c0-8d2b7e4f16a9"
}
The replay of a single delivery never returns 409. It returns 200 and silently does nothing when the delivery is PENDING or RETRYING or the endpoint’s circuit is OPEN. A client branching on 409 to detect a refused replay will swallow every one of them. See Deliveries.
429
The endpoint’s delivery rate budget for the current window is spent. The budget is shared with that endpoint’s real deliveries rather than being a separate allowance for testing, so a test can never starve the production traffic for the endpoint it is meant to be validating. Try again in about a second.
{
"type": "https://pointservices.com/problems/too-many-requests",
"title": "The delivery rate budget is spent",
"status": 429,
"detail": "This endpoint's per second delivery budget for the current window is spent. The budget is shared with its real deliveries. Try again in about a second.",
"instance": "urn:pps:request:7e19c3b8-4d06-4a2f-b95c-1c8e6f30d472"
}
503
A fault on the PPS side, never a statement about your endpoint. Most concretely, the PPS signer could not produce a signature. Your endpoint was not contacted, so nothing was delivered in part and the request is safe to retry as it stands. This status has no dedicated problem type, so it carries about:blank.
The test operation is the only one that returns it. A real delivery that cannot be signed is not sent and is retried on the ordinary schedule instead, which you observe on the delivery row rather than as an HTTP status.
{
"type": "about:blank",
"title": "Service Unavailable",
"status": 503,
"detail": "PPS could not produce a signature for this delivery, so nothing was sent to your endpoint. Retry the request.",
"instance": "urn:pps:request:0b4d7f31-9a52-4c68-8e10-3f7b2d5c9a84"
}
Reporting a problem
instance is written as urn:pps:request:<id>, and the id inside it is the request correlation id. It is the same value returned in the x-pps-request-id and X-PPS-CorrelationID response headers. Quote it when you raise a support ticket; it is what lets PPS find that exact request.