Beta
Register An Endpoint
Create a webhook endpoint in an org unit you hold registration rights for.
POST /v1/webhooks/endpoints
curl -X POST https://api.pointservices.com/riskinsight-services-ws/resources/v1/webhooks/endpoints \
-H "Authorization: Bearer your_access_token_here" \
-H "Content-Type: application/json" \
-d '{
"url": "https://hooks.example.com/pps/workflows",
"organizationalUnitGuid": "OU41502",
"displayName": "Production order hook",
"acceptedEventTypes": [
{
"serviceName": "ADV",
"modelName": "ADV-120",
"eventTypes": ["workflow.updated", "workflow.failed"]
}
],
"contacts": ["integrations@example.com"],
"signatureMode": "SYMMETRIC",
"signingSecret": "whsec_REPLACE_WITH_YOUR_OWN_RANDOM_KEY_MATERIAL"
}'
The same request body on its own, so you can copy it without unpicking the shell quoting.
{
"url": "https://hooks.example.com/pps/workflows",
"organizationalUnitGuid": "OU41502",
"displayName": "Production order hook",
"acceptedEventTypes": [
{
"serviceName": "ADV",
"modelName": "ADV-120",
"eventTypes": ["workflow.updated", "workflow.failed"]
}
],
"contacts": ["integrations@example.com"],
"signatureMode": "SYMMETRIC",
"signingSecret": "whsec_REPLACE_WITH_YOUR_OWN_RANDOM_KEY_MATERIAL"
}
organizationalUnitGuid is required and names the org unit the endpoint belongs to. There is no default, so a body omitting it is rejected with a 400. An org unit outside your hierarchy, or one you hold no registration rights for, is rejected with a 403 rather than falling back to your own.
PPS checks that the URL’s domain is authorized, and runs destination-safety validation, before the endpoint is created. An unauthorized domain, or one that fails validation, is rejected with a 400 rather than accepted in a pending state. HTTPS is required.
Supply signingSecret when signatureMode is SYMMETRIC, including when you omit signatureMode entirely, since SYMMETRIC is the default. An ASYMMETRIC endpoint holds no shared secret, so supplying one is rejected rather than ignored.
whsec_REPLACE_WITH_YOUR_OWN_RANDOM_KEY_MATERIAL is a placeholder, not key material, and PPS rejects it with a 400. Generate your own secret from a cryptographically secure random source, one per endpoint; Getting Started shows a command that does.
Header Properties
| Property | Value | Required? |
|---|---|---|
| Authorization | Bearer your_access_token_here | true |
| Content-Type | application/json | true |
Request Data Properties
| Property | Description | Type | Required |
|---|---|---|---|
| url | The destination deliveries are POSTed to. Its domain must be one PPS has authorized, and it is checked against destination-safety rules; a URL that fails either is rejected with a 400 and the endpoint is not created. HTTPS is required. | string | yes |
| organizationalUnitGuid | The org unit the endpoint is created in, written as a string like OU41502. There is no default. Your PPS account manager supplies this value. | string | yes |
| displayName | Your own label. Trimmed, and the trimmed value must be 255 characters or fewer. | string | no |
| acceptedEventTypes | What this endpoint accepts, declared per product. Sending [] means “accept nothing”, which registers an endpoint that receives nothing. | array | no |
| contacts | Notification contact email addresses, used to tell you when PPS stops delivering to this endpoint because its circuit opened. No email is sent when delivery resumes; read the endpoint’s circuitState to see that it is CLOSED again. Registering none is permitted, but then nobody is told and deliveries stop silently. Each value is trimmed and normalized to lowercase before storage. | array | no |
| enabled | Whether the endpoint is switched on. A newly registered endpoint is enabled regardless of this property. | boolean | no |
| signatureMode | SYMMETRIC or ASYMMETRIC. Defaults to SYMMETRIC. | string | no |
| signingSecret | The secret PPS signs this endpoint’s deliveries with: the whsec_ prefix followed by 24 to 64 bytes of key material in base64. Required when signatureMode is SYMMETRIC, including when you omit the mode entirely; must be absent when it is ASYMMETRIC. | string | conditional |
acceptedEventTypes Entry Properties
| Property | Description | Type | Required |
|---|---|---|---|
| serviceName | The product’s service name, exactly as you name it when starting a workflow. An unrecognized value is rejected with a 400. | string | no |
| modelName | The product’s model name. Stored as given and matched exactly at delivery time, so a mistyped model receives nothing rather than being rejected. | string | no |
| eventTypes | The topics you want for this product, from the eight in Event Topics: workflow.pending, workflow.updated, workflow.failed, finding.result_updated, finding.reviewed, finding.note_updated, finding.attachment_updated and finding.attachment_removed. Repeats are collapsed. | array | no |
An empty eventTypes array is permitted and means this entry accepts nothing, which is equivalent to omitting the entry.
Responses
200
The created endpoint, with no secret in it. A successful register returns 200, not 201.
| Property | Description | Type | Notes |
|---|---|---|---|
| id | The endpoint’s id. It is the handle every other call that names an endpoint takes. | number | |
| organizationalUnitGuid | The org unit this endpoint belongs to, read back from the stored endpoint rather than echoed from your request. | string | |
| displayName | Your own label. Absent when none was ever set. | string | |
| url | The destination deliveries are POSTed to. | string | |
| enabled | Whether the endpoint is switched on. Only one of the three conditions behind receiving. | boolean | |
| circuitState | CLOSED or OPEN. A freshly registered endpoint reports CLOSED. | string | |
| receiving | Whether nothing stands between this endpoint and delivery: it is enabled, its circuit is CLOSED, and it has declared at least one product and topic. | boolean | |
| notReceivingReason | Why receiving is false: NOT_DECLARED, NO_GRANT, DISABLED or CIRCUIT_OPEN. Absent when receiving is true, never null and never a NONE sentinel. | string | conditional |
| hasNoContactsRegistered | True when zero notification contacts are registered. A permitted configuration, but the notification sent when this endpoint’s circuit opens would then reach nobody, and you would not learn that deliveries had stopped. | boolean | |
| signatureMode | SYMMETRIC or ASYMMETRIC. | string | |
| acceptedEventTypes | What this endpoint has declared it accepts, grouped by product. Always present, including as []. | array | always emitted |
| warnings | Advisories that block nothing, as English sentences. Write paths always emit it, including as []. | array | write paths only |
There is no signingSecret property on this schema, and no other response schema in this API has one either. A signing secret travels from you to PPS and never back.
Example:
{
"id": 4711,
"organizationalUnitGuid": "OU41502",
"displayName": "Production order hook",
"url": "https://hooks.example.com/pps/workflows",
"enabled": true,
"circuitState": "CLOSED",
"receiving": true,
"hasNoContactsRegistered": false,
"signatureMode": "SYMMETRIC",
"acceptedEventTypes": [
{
"serviceName": "ADV",
"modelName": "ADV-120",
"eventTypes": ["workflow.updated", "workflow.failed"]
}
],
"warnings": []
}
A newly registered endpoint is enabled. It will not receive anything until it declares at least one accepted event type, so read receiving rather than enabled.
Registering without acceptedEventTypes is allowed, and returns a warnings entry saying the endpoint receives nothing.
{
"id": 4711,
"organizationalUnitGuid": "OU41502",
"displayName": "Production order hook",
"url": "https://hooks.example.com/pps/workflows",
"enabled": true,
"circuitState": "CLOSED",
"receiving": false,
"notReceivingReason": "NOT_DECLARED",
"hasNoContactsRegistered": false,
"signatureMode": "SYMMETRIC",
"acceptedEventTypes": [],
"warnings": [
"This endpoint declares no accepted event types, so it will receive nothing until you add some."
]
}
A misspelled acceptedEventTypes registers the same receive-nothing endpoint that omitting it does. Unknown keys are dropped rather than rejected, so acceptedEventTyps produces a 200 and an endpoint that never receives anything. Compare the acceptedEventTypes echoed back against what you sent.
Registering into a child org unit succeeds, and the endpoint then receives that child org unit’s own events, not the parent’s. An org unit’s events reach its own endpoints without a grant. Events produced in any other org unit, the parent included, reach the endpoint only through a subscription grant that names the child org unit as its destination: grants match the endpoint’s org unit exactly and do not inherit down the tree.
Read organizationalUnitGuid and receiving back off the response to confirm the endpoint landed where you intended. An endpoint that has declared a product and topic reports receiving: true, as here. That tells you it is wired up for its own org unit’s events. It does not tell you that any other org unit routes events to it.
{
"id": 4711,
"organizationalUnitGuid": "OU41507",
"displayName": "Production order hook",
"url": "https://hooks.example.com/pps/workflows",
"enabled": true,
"circuitState": "CLOSED",
"receiving": true,
"hasNoContactsRegistered": false,
"signatureMode": "SYMMETRIC",
"acceptedEventTypes": [
{
"serviceName": "ADV",
"modelName": "ADV-120",
"eventTypes": ["workflow.updated", "workflow.failed"]
}
],
"warnings": []
}
Accepting workflow.updated without workflow.failed is permitted and returns a warning rather than an error.
{
"id": 4711,
"organizationalUnitGuid": "OU41502",
"displayName": "Production order hook",
"url": "https://hooks.example.com/pps/workflows",
"enabled": true,
"circuitState": "CLOSED",
"receiving": true,
"hasNoContactsRegistered": false,
"signatureMode": "SYMMETRIC",
"acceptedEventTypes": [
{
"serviceName": "ADV",
"modelName": "ADV-120",
"eventTypes": ["workflow.updated"]
}
],
"warnings": [
"This endpoint accepts workflow.updated for ADV/ADV-120 without workflow.failed, so it will not be told when a run fails."
]
}
400
organizationalUnitGuid omitted.
{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"detail": "organizationalUnitGuid is required",
"instance": "urn:pps:request:c40a92e7-1b58-4d73-8f26-5a9e3c0b7d41"
}
A URL whose domain PPS has not authorized, or which fails destination-safety validation, is also rejected here. The endpoint is not created and there is no pending state to wait in.
{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"detail": "Webhook URL host is not an approved destination: hooks.example.net. Contact PPS support to request approval.",
"instance": "urn:pps:request:5a7d02be-63c1-4f89-b204-9e138c7a06f5"
}
Both 400s above carry type: "about:blank", so you cannot branch on type to tell them apart. They are identical except for detail, which is prose written for a person and is not a stable code. Branch on the status, then read detail to log what went wrong; do not pattern-match its text. Two 400s do carry a dedicated type: https://pointservices.com/problems/invalid-request for a Content-Type naming a character set the service cannot use, and https://iana.org/assignments/http-problem-types#digest-mismatched-values for a body that did not match its digest. A domain rejection is not something you can fix in the request — ask your PPS account manager to authorize the domain, then register again.
403
An org unit you hold no registration rights over — OU90311 here. The endpoint is not created, and PPS does not fall back to your own org unit. This is a 403 because it is about what you may act on; a rejected destination domain is a 400 because it is about the request. A blank organizationalUnitGuid is a 400 too, for the same reason.
{
"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:6b17d9f4-2e83-4a05-9c7b-8d1f0e5a3c62"
}
detail is fixed prose and never names the org unit it refused, so read the org unit you sent rather than parsing it out of the message.
401
No credential was presented, or the one presented was rejected.
409
The resource’s current state refuses the request, or it changed under you.
See Errors for the invalid-token, about:blank and concurrent-modification bodies.
List Endpoints
Return a page of the webhook endpoints you may see, ascending by endpoint id.
GET /v1/webhooks/endpoints
curl -X GET "https://api.pointservices.com/riskinsight-services-ws/resources/v1/webhooks/endpoints?marker=4711&limit=50" \
-H "Authorization: Bearer your_access_token_here"
Header Properties
| Property | Value | Required? |
|---|---|---|
| Authorization | Bearer your_access_token_here | true |
Query Parameters
| Property | Description | Type | Default |
|---|---|---|---|
| marker | The pagination cursor: pass back the nextMarker from a previous response. It is exclusive, so the endpoint it names is not sent again, and paging proceeds toward newer endpoints. A string on the wire that must parse as a positive integer; a value that will not is rejected with a 400. | 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 |
limit=0 and limit=5000 both yield 100. A value outside the range is replaced outright by the default rather than clamped or rejected. The API reports the page size it applied rather than guessing what you meant, so compare the response’s own limit against what you sent.
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 |
| count | How many endpoints this page carries. | number | always emitted, including 0 |
| nextMarker | An opaque resume position, present whenever more endpoints may exist to see. | string | conditional |
| results | The endpoints, always in the shape that carries no secret. | array | always emitted, including [] |
A page can come back short, or even completely empty, and still carry nextMarker. Access is decided per endpoint after rows are read, so the server bounds how much work it will do for one request. Stop paging only when nextMarker is absent, never on a short or empty page alone.
This is what that looks like: one endpoint under a limit of 50, and a cursor anyway, because there is more to see.
{
"limit": 50,
"marker": "4711",
"count": 1,
"nextMarker": "4783",
"results": [
{
"id": 4712,
"organizationalUnitGuid": "OU41502",
"displayName": "Production order hook (EU)",
"url": "https://hooks.example.com/pps/workflows",
"enabled": true,
"circuitState": "CLOSED",
"receiving": true,
"hasNoContactsRegistered": false,
"signatureMode": "SYMMETRIC",
"acceptedEventTypes": [
{
"serviceName": "ADV",
"modelName": "ADV-120",
"eventTypes": ["workflow.updated", "workflow.failed"]
}
]
}
]
}
nextMarker here is 4783, which is not the id of any endpoint in results. On a full page the cursor is the last endpoint returned; after a bounded scan it is instead the position the scan reached, which need not appear in results at all and may not correspond to any endpoint you can see. Treat it as opaque in both cases and pass it back unmodified. A loop that stopped because count was less than limit would have stopped here and missed everything the scan had not yet reached.
Entries carry no warnings key, because this is a read path.
Example:
{
"limit": 50,
"marker": "4710",
"count": 2,
"nextMarker": "4712",
"results": [
{
"id": 4711,
"organizationalUnitGuid": "OU41502",
"displayName": "Production order hook",
"url": "https://hooks.example.com/pps/workflows",
"enabled": true,
"circuitState": "CLOSED",
"receiving": true,
"hasNoContactsRegistered": false,
"signatureMode": "SYMMETRIC",
"acceptedEventTypes": [
{
"serviceName": "ADV",
"modelName": "ADV-120",
"eventTypes": ["workflow.updated", "workflow.failed"]
}
]
},
{
"id": 4712,
"organizationalUnitGuid": "OU41502",
"displayName": "Production order hook (EU)",
"url": "https://hooks.example.com/pps/workflows",
"enabled": true,
"circuitState": "CLOSED",
"receiving": true,
"hasNoContactsRegistered": false,
"signatureMode": "SYMMETRIC",
"acceptedEventTypes": [
{
"serviceName": "ADV",
"modelName": "ADV-120",
"eventTypes": ["workflow.updated", "workflow.failed"]
}
]
}
]
}
A request sending limit=5000 comes back with limit: 100, which is how you see that what you sent was replaced.
{
"limit": 100,
"count": 1,
"results": [
{
"id": 4711,
"organizationalUnitGuid": "OU41502",
"displayName": "Production order hook",
"url": "https://hooks.example.com/pps/workflows",
"enabled": true,
"circuitState": "CLOSED",
"receiving": true,
"hasNoContactsRegistered": false,
"signatureMode": "SYMMETRIC",
"acceptedEventTypes": [
{
"serviceName": "ADV",
"modelName": "ADV-120",
"eventTypes": ["workflow.updated", "workflow.failed"]
}
]
}
]
}
400
The request body was malformed or carried a value outside this API’s vocabulary.
401
No credential was presented, or the one presented was rejected.
See Errors.
Get One Endpoint
Return one endpoint in the shape that carries no secret.
GET /v1/webhooks/endpoints/{id}
curl -X GET "https://api.pointservices.com/riskinsight-services-ws/resources/v1/webhooks/endpoints/4711" \
-H "Authorization: Bearer your_access_token_here"
Path Parameters
| Property | Description | Type | Required |
|---|---|---|---|
| id | The endpoint’s id. | number | yes |
Responses
200
The same shape the register operation returns, minus warnings.
Example:
{
"id": 4711,
"organizationalUnitGuid": "OU41502",
"displayName": "Production order hook",
"url": "https://hooks.example.com/pps/workflows",
"enabled": true,
"circuitState": "CLOSED",
"receiving": true,
"hasNoContactsRegistered": false,
"signatureMode": "SYMMETRIC",
"acceptedEventTypes": [
{
"serviceName": "ADV",
"modelName": "ADV-120",
"eventTypes": ["workflow.updated", "workflow.failed"]
}
]
}
This body carries no warnings key at all. Read paths never emit it, while write paths always do, including as []. An absent key and [] are different statements: warnings: [] means your configuration produced no advisories, and an absent key means the response did not come from a write path.
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, which is why an endpoint that is not yours answers 404 rather than 403. Check the id you sent and the org unit of the credential you sent it with.
See Errors.
Edit An Endpoint
Update an endpoint. Every property of the request body is optional, and an omitted property is left unchanged.
POST /v1/webhooks/endpoints/{id}
curl -X POST https://api.pointservices.com/riskinsight-services-ws/resources/v1/webhooks/endpoints/4711 \
-H "Authorization: Bearer your_access_token_here" \
-H "Content-Type: application/json" \
-d '{
"displayName": "Production order hook (EU)",
"acceptedEventTypes": [
{
"serviceName": "ADV",
"modelName": "ADV-120",
"eventTypes": [
"workflow.updated",
"workflow.failed",
"workflow.pending",
"finding.reviewed",
"finding.result_updated",
"finding.note_updated",
"finding.attachment_updated",
"finding.attachment_removed"
]
}
]
}'
Those are merge semantics: this is a POST rather than a PUT because it updates the properties you send and leaves the rest alone, which is not what PUT promises.
An edit is validated as strictly as a create. Any URL change runs the same destination-safety validation again, and a change of URL must land on an authorized domain or the entire edit is rejected with a 400, with no part of it applied. There is no state in which an edit waits to be authorized.
Header Properties
| Property | Value | Required? |
|---|---|---|
| Authorization | Bearer your_access_token_here | true |
| Content-Type | application/json | true |
Request Data Properties
| Property | Description | Type | Required |
|---|---|---|---|
| url | The destination deliveries are POSTed to. | string | no |
| organizationalUnitGuid | A different value moves the endpoint to that org unit. Omitting it, or sending the value it already has, leaves it where it is. | string | no |
| displayName | Your own label. | string | no |
| acceptedEventTypes | Omitting it leaves the declared set alone; sending [] replaces it with nothing. | array | no |
| contacts | Notification contact email addresses, used to tell you when this endpoint’s circuit opens. | array | no |
| enabled | Only the JSON literals true and false are accepted. A number, including 0 or 1, or a quoted string is rejected with a 400. | boolean | no |
| signatureMode | SYMMETRIC or ASYMMETRIC. Send it with signingSecret to switch modes. | string | no |
| signingSecret | Sent alone, rotates the secret of an endpoint that is already SYMMETRIC. Sent alone to an ASYMMETRIC endpoint, rejected with a 400. | string | no |
Omitted versus explicitly empty
For the array properties, an omitted key and an explicitly empty array are different instructions. This body changes the label and leaves the declared topics as they are.
{
"displayName": "Production order hook (EU)"
}
This body changes the label and clears the declared topics, after which the endpoint receives nothing.
{
"displayName": "Production order hook (EU)",
"acceptedEventTypes": []
}
Rotating the signing secret
A rotated signing secret takes effect immediately, including for deliveries already queued and awaiting an attempt, and there is no overlap window in which both the old and the new secret verify. Install the new value in your consumer first, then call this. In the other order there is an interval during which you cannot verify what PPS sends.
curl -X POST https://api.pointservices.com/riskinsight-services-ws/resources/v1/webhooks/endpoints/4711 \
-H "Authorization: Bearer your_access_token_here" \
-H "Content-Type: application/json" \
-d '{
"signingSecret": "whsec_REPLACE_WITH_YOUR_OWN_RANDOM_KEY_MATERIAL"
}'
Choose the value from a cryptographically secure random source. PPS checks that it is well formed — the whsec_ prefix followed by 24 to 64 bytes of key material in base64 — but cannot check that it is unguessable. Treat it as a credential: do not send it over any channel but this API, and do not paste it into a support ticket.
Switching signature mode
Send signatureMode in the same request as the secret change. Switching to SYMMETRIC requires signingSecret; switching to ASYMMETRIC forbids it and clears the stored value, so that request carries signatureMode alone. One request makes the transition atomic, with no interval in which the endpoint is SYMMETRIC but unsignable.
curl -X POST https://api.pointservices.com/riskinsight-services-ws/resources/v1/webhooks/endpoints/4711 \
-H "Authorization: Bearer your_access_token_here" \
-H "Content-Type: application/json" \
-d '{
"signatureMode": "ASYMMETRIC"
}'
Moving to another org unit
Moving an endpoint changes which events it receives, and this is the one consequence the API cannot warn you about at the time. An endpoint receives the events of the org unit it belongs to, plus those a subscription grant routes to that exact org unit. The moment it lands elsewhere it stops seeing the events it was built for, including any a grant routed to its old org unit, and starts seeing the destination’s instead. The move returns 200 and the endpoint still reports itself enabled and receiving: true, so nothing in the response looks wrong while your integration goes quiet. Confirm the destination is the org unit whose events you actually want, and, if you rely on another org unit’s events, that a grant names the destination, before you move.
curl -X POST https://api.pointservices.com/riskinsight-services-ws/resources/v1/webhooks/endpoints/4711 \
-H "Authorization: Bearer your_access_token_here" \
-H "Content-Type: application/json" \
-d '{
"organizationalUnitGuid": "OU41507"
}'
The move request body on its own.
{
"organizationalUnitGuid": "OU41507"
}
The destination is authorized in its own right, not merely the endpoint you already own. Moving into an org unit outside your hierarchy, or one you hold no rights over, is refused with a 403 and the endpoint is left exactly where it was. Deliveries already queued for the endpoint are not rewritten by a move.
Disabling an endpoint
curl -X POST https://api.pointservices.com/riskinsight-services-ws/resources/v1/webhooks/endpoints/4711 \
-H "Authorization: Bearer your_access_token_here" \
-H "Content-Type: application/json" \
-d '{
"enabled": false
}'
There is no operation that deletes an endpoint. Disabling is how you take one out of service, and the endpoint continues to exist and to appear in your endpoint listing. Disabling stops all deliveries, and deliveries queued at the moment of disabling are parked, not cancelled. Enabling the endpoint again and then issuing a bulk replay resurrects them. Recovery is bounded by the delivery retention window, which is 90 days, so parked rows are eventually reaped like any other finished row. See Deliveries.
Responses
200
The updated endpoint, with no secret in it. Same shape as the register response.
Example, after the edit at the top of this section:
{
"id": 4711,
"organizationalUnitGuid": "OU41502",
"displayName": "Production order hook (EU)",
"url": "https://hooks.example.com/pps/workflows",
"enabled": true,
"circuitState": "CLOSED",
"receiving": true,
"hasNoContactsRegistered": false,
"signatureMode": "SYMMETRIC",
"acceptedEventTypes": [
{
"serviceName": "ADV",
"modelName": "ADV-120",
"eventTypes": [
"workflow.updated",
"workflow.failed",
"workflow.pending",
"finding.reviewed",
"finding.result_updated",
"finding.note_updated",
"finding.attachment_updated",
"finding.attachment_removed"
]
}
],
"warnings": []
}
After a move, the endpoint is still enabled and still reports receiving: true, even if the destination org unit never produces the events it has declared. receiving says nothing stands between the endpoint and delivery; it cannot say whether an org unit will produce events.
{
"id": 4711,
"organizationalUnitGuid": "OU41507",
"displayName": "Production order hook",
"url": "https://hooks.example.com/pps/workflows",
"enabled": true,
"circuitState": "CLOSED",
"receiving": true,
"hasNoContactsRegistered": false,
"signatureMode": "SYMMETRIC",
"acceptedEventTypes": [
{
"serviceName": "ADV",
"modelName": "ADV-120",
"eventTypes": ["workflow.updated", "workflow.failed"]
}
],
"warnings": []
}
400
signingSecret sent alone to an ASYMMETRIC endpoint.
{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"detail": "Endpoint 4711 is ASYMMETRIC and holds no shared secret, so `signingSecret` cannot be set on its own. Send `signatureMode` and `signingSecret` together to switch it to SYMMETRIC.",
"instance": "urn:pps:request:d82e50a9-4c17-4b63-97fa-0e6b3d1c8547"
}
enabled sent as the number 0.
{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"detail": "`enabled` must be the JSON literal true or false. It carried the number 0, which is not converted to a boolean.",
"instance": "urn:pps:request:9e0b4a37-6d52-4f18-8c94-2a7f5e3b0d61"
}
403
A move to OU90311, a destination outside your hierarchy. The endpoint stays where it was. This is the loud failure; the silent one is a move into an org unit you may reach but that produces nothing the endpoint has declared, which returns 200.
{
"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:f93c05b6-7a21-4e48-b0d9-3c6e1a84f572"
}
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, which is why an endpoint that is not yours answers 404 rather than 403. Check the id you sent and the org unit of the credential you sent it with.
409
The resource’s current state refuses the request, or it changed under you.
See Errors.
Endpoint Vocabularies
signatureMode
| Value | Signature entry | Key material | Key rotation |
|---|---|---|---|
| SYMMETRIC | v1, HMAC-SHA256 | Your signingSecret, which you set and rotate | You rotate the secret and install it in your consumer first |
| ASYMMETRIC | v1a, Ed25519 | The PPS published public key, fetched through OIDC discovery | PPS rotates its key and you change nothing |
SYMMETRIC is the default, so an endpoint registered without mentioning signing at all is a symmetric one and must carry a secret from the moment it exists.
An ASYMMETRIC endpoint holds no shared secret, so if PPS cannot produce the v1a signature the delivery is not sent at all and is retried on the ordinary schedule. It is never downgraded to an unsigned request and never to v1.
circuitState
| Value | Meaning |
|---|---|
| CLOSED | The healthy state. Deliveries are attempted normally, and a freshly registered endpoint reports CLOSED. |
| OPEN | PPS has stopped attempting deliveries because the endpoint failed repeatedly. Deliveries are held rather than dropped. |
There are exactly two states. There is no third.
| Event | Effect on the circuit |
|---|---|
| Five consecutive failed attempts with no success between them | Opens it. The unit is the attempt, not the delivery, and there is no time window. |
Your endpoint answers 410 Gone | Opens it. |
| One delivery exhausts its full retry schedule | Opens it. That is ten attempts over about 75 hours; see the retry schedule. |
| A successful attempt | Resets the failure run to zero. |
| One successful delivery while the circuit is open | Closes it, and the held deliveries resume. |
| Five minutes elapsed since it opened | One probe delivery is admitted, one in flight at a time. |
| A successful test delivery | Closes it. A test is not subject to the circuit, and a failed test never counts against it. |
A failure is a transport error or any response outside the 2xx range, including a 4xx. Only failures newer than the endpoint’s most recent successful attempt are counted. The probe is admitted without changing the reported state, so an endpoint under trial still reports OPEN until the probe succeeds.
notReceivingReason
| Value | Meaning | Who fixes it |
|---|---|---|
| NOT_DECLARED | The endpoint declares no accepted event types, so no event can ever match it. | You, by adding products and topics to acceptedEventTypes. |
| DISABLED | The endpoint has declared something, but it is switched off (enabled: false). PPS does not switch an endpoint off after failures; it opens the circuit instead. | You, by re-enabling it. |
| CIRCUIT_OPEN | Declared and enabled, but the circuit breaker opened after repeated delivery failures or a 410 Gone. | It closes on its own once your endpoint accepts a probe, or when a test delivery succeeds. |
| NO_GRANT | The endpoint declared products and topics, but no live subscription grant covers any of them. An endpoint that has declared anything receives its own org unit’s events without a grant, so this value is not expected in practice. Handle it anyway. | PPS. Contact your PPS account manager. |
receiving is true only when the endpoint is enabled, its circuit is CLOSED, and it has declared at least one product and topic in its acceptedEventTypes. It is one answer for the whole endpoint, not one per product: an endpoint that declared ADV-120 reports receiving: true whatever else you expect it to receive. Declaring is the only step you take for your own org unit’s events. A subscription grant is needed only for events produced in another org unit, and receiving does not tell you whether one exists.
Exactly one cause is reported in notReceivingReason even when several hold at once, in this order: NOT_DECLARED, then DISABLED, then CIRCUIT_OPEN, then NO_GRANT. Work the reported cause, then read the field again. Treat an unrecognized value as “not receiving, cause unknown” so a reason added later does not break your client.
organizationalUnitGuid versus sourceOuGuid
Both are OU-prefixed strings, and org unit GUIDs are globally unique, so the two are told apart by where they appear rather than by their shape.
| Field | Where it appears | What it names |
|---|---|---|
organizationalUnitGuid | Register and edit requests; every endpoint response | The org unit the endpoint belongs to, which is the destination its deliveries are routed to. OU41502 in these docs. |
sourceOuGuid | deliveries[] on a simulate response | The org unit the simulated event was attributed to: your own org unit (OU41502) for the event PPS always fires, or a granting org unit such as OU10432 for the others. |
Getting Started walks through registering an endpoint end to end, including where your org unit GUID comes from.