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.


Copyright © Pitchpoint Solutions. All rights reserved.