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.

Browse every operation, its schemas and code samples in the API Explorer, or download the OpenAPI specification for your own tools.

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 mechanism this API supports; send your credential this way and no other. 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 the service sends 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. The one exception comes from in front of the service: the platform edge can refuse a request with a 403 and an HTML body, for example when it omits Content-Type or carries a value that resembles SQL or script. Check Content-Type before you parse an error body.

{
  "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: most 400s carry it, as do a 401 with no credential, a 403 refused before the operation ran, and every 5xx. A 401 with a rejected token, a 403 decided by the operation, and every 404, 409 and 429, carries a dedicated type, and so do two kinds of 400. 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. These 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://pointservices.com/problems/invalid-request 400 The Content-Type header names a character set this service cannot use. Correct the charset parameter of your Content-Type header.
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, 403 refused before the operation ran, and any status with no dedicated type of its own, including every 5xx No credential was presented, the request was refused before the operation ran, 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 involved: the one you named when registering or moving an endpoint, or your own when simulating. 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 Another write landed first, so nothing was applied. Read the resource again, and resubmit if your change still applies.
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. These 400s carry about:blank, so detail is the only thing that tells two of them apart.

Two 400s carry a dedicated type. https://pointservices.com/problems/invalid-request means the Content-Type header names a character set this service cannot use. https://iana.org/assignments/http-problem-types#digest-mismatched-values means the body did not match the digest sent with it.

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 are authenticated, and the answer is still no. A 403 is an authorization decision, never a statement about your credential, so refreshing your token will not turn it into a 200. A request refused with a 403 changes nothing.

Any operation can return one. Two types tell the cases apart:

  • https://pointservices.com/problems/not-authorized: the operation refused the org unit involved. Registering an endpoint in, or moving one to, an org unit you hold no rights over returns it, and so does simulating an event when your credential may not manage webhooks in its own org unit.
  • about:blank: the request was refused before the operation ran.

The platform edge can also answer 403 before the request reaches the service, with an HTML body rather than a problem document.

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 another write landed first, so your write was not applied. It is not retried for you: read the resource again, decide whether your change still applies to what you now see, and send the edit again if it does.

{
  "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.


Table of contents


Copyright © Pitchpoint Solutions. All rights reserved.