API REFERENCE
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/orders",
"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_Kv9mQ3xT7pYbN2sW8gLdR5hJ4cF6nZaE1uXoP0iVtB0="
}'
The same request body on its own, so you can copy it without unpicking the shell quoting.
{
"url": "https://hooks.example.com/pps/orders",
"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_Kv9mQ3xT7pYbN2sW8gLdR5hJ4cF6nZaE1uXoP0iVtB0="
}
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.
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. 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: workflow.updated, workflow.failed. 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 an event fired right now would reach this endpoint: enabled, circuit CLOSED, and the product and topic declared. | 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. | 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/orders",
"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/orders",
"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, and nothing inherits up or down the tree: an endpoint in a department does not see the parent company’s events. Both org units are in your own tenant, so no subscription grant is involved either way.
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 in its own org unit, as here.
{
"id": 4711,
"organizationalUnitGuid": "OU41507",
"displayName": "Production order hook",
"url": "https://hooks.example.com/pps/orders",
"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/orders",
"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"
}
A 400 from this API carries type: "about:blank", so you cannot branch on type to tell one 400 from another. Every 400 above is 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. 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 your tenant owns, 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/orders",
"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/orders",
"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/orders",
"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/orders",
"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/orders",
"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, and none of them answers 403.
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"]
}
]
}'
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. | 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_Kv9mQ3xT7pYbN2sW8gLdR5hJ4cF6nZaE1uXoP0iVtB0="
}'
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 org unit’s 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, so the moment it lands elsewhere it stops seeing the events it was built for and starts seeing the destination’s instead. The move returns 200 and the endpoint still reports itself enabled and, usually, receiving: true — so nothing in the response looks wrong while your integration goes quiet. Read organizationalUnitGuid and receiving off the response and confirm the destination is the org unit whose events you actually want.
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:
{
"id": 4711,
"organizationalUnitGuid": "OU41502",
"displayName": "Production order hook (EU)",
"url": "https://hooks.example.com/pps/orders",
"enabled": true,
"circuitState": "CLOSED",
"receiving": true,
"hasNoContactsRegistered": false,
"signatureMode": "SYMMETRIC",
"acceptedEventTypes": [
{
"serviceName": "ADV",
"modelName": "ADV-120",
"eventTypes": ["workflow.updated", "workflow.failed"]
}
],
"warnings": []
}
After a move to an org unit that produces nothing the endpoint has declared, it is still enabled but no longer receiving.
{
"id": 4711,
"organizationalUnitGuid": "OU41507",
"displayName": "Production order hook",
"url": "https://hooks.example.com/pps/orders",
"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, and none of them answers 403.
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. |
| NO_GRANT | No live subscription grant covers the endpoint’s declared selectors. Rare: your own tenant’s data needs no grant, so this applies only to cross-tenant delivery. | PPS. Contact your PPS account manager. |
| DISABLED | Fully provisioned, but the endpoint is switched off, either by you or by PPS after repeated failures. | You, by re-enabling it. |
| CIRCUIT_OPEN | Fully provisioned and enabled, but the circuit breaker opened after repeated delivery failures. | It closes on its own once your endpoint responds successfully. |
receiving is true only when the endpoint is enabled, its circuit is CLOSED, and the endpoint has declared the product and topic in its acceptedEventTypes. Declaring is the only step you take for data originating in your own tenant. A subscription grant is needed only to receive another tenant’s data. Exactly one cause is reported in notReceivingReason even when several hold at once, and it is the one closest to the root of the dependency chain: consent — NOT_DECLARED, then NO_GRANT — outranks the endpoint’s own state — DISABLED, then CIRCUIT_OPEN. 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. Org unit GUIDs are globally unique, so the two are told apart by which tenant they belong to rather than by their shape.
| Field | Where it appears | What it names |
|---|---|---|
organizationalUnitGuid | Register and edit requests; every endpoint response | An org unit in your own tenant — the destination an endpoint’s deliveries are routed to. OU41502 in these docs. |
sourceOuGuid | deliveries[] on a simulate response | An org unit in the data owner’s tenant — where the event originated. Never one of yours. OU10432 in these docs. |
Getting Started walks through registering an endpoint end to end, including where your org unit GUID comes from.