> For the complete documentation index, see [llms.txt](https://peoplescourt.ai/llms.txt). This page is also available as Markdown.

# Keeping in sync

Partner callbacks are informational, sparse, and delivered at least once. They are not commands and must not be used as the sole source of case or finality state.

## Recover with the events cursor

Poll `GET /api/cases/{id}/events` with a credential authorized for the case. OAuth requires `cases:read`. The response’s `data` contains `schemaVersion: case-event-page-v1`, `caseId`, `events`, `nextCursor`, and `hasMore`. Each event contains `schemaVersion: case-event-v1`, `id`, `cursor`, `type`, and `occurredAt`.

| Query parameter | Required | Meaning |
|---|---|---|
| `after` | No | The last durably processed cursor from this case and authorized stream. Omit on the first read. |
| `limit` | No | Integer from 1 to 100; defaults to 50. |

Process a page, persist `nextCursor` with your work, and pass it as `after` on the next read. Continue while `hasMore` is true. An empty page retains the input cursor, or returns `null` on an initial empty read. Cursors are opaque; do not sort or increment them. An unavailable cursor returns `422 invalid_event_cursor`; refresh the case and restart the stream with durable event-ID deduplication.

Webhook deliveries include `caseEvent.id` and `caseEvent.cursor`. Use the ID to reconcile a callback with a polled event. Do not advance your polling checkpoint to an out-of-order callback: drain the stream from the last committed checkpoint first. The callback’s `eventId` remains its delivery-deduplication identity. Older callbacks may have no `caseEvent`.

Events expose only the caller’s permitted record. Fetch `/api/cases/{id}` after a notification to read current state; polling does not grant access to sealed evidence.

## Consumer algorithm

1. Verify the Ed25519 signature using the published key set.
2. Deduplicate using body/header `eventId`, not `deliveryId`.
3. Check `x-pc-event-timestamp` against the replay window.
4. Return a successful response promptly.
5. Fetch the authoritative authenticated case, decision, or Award resource.
6. Verify Award signatures before initiating any separately authorized settlement action.

Settlement events are intentionally distinct: `decision_served` means a signed decision is available but does not prove execution; `settlement_completed`, `settlement_failed`, `settlement_blocked`, and `settlement_manual_action_required` report the execution boundary outcome. Never treat decision service as settlement completion.

`procedural_message_received` is also sparse: fetch `GET /api/cases/{id}/messages?channel=partner_notice` for the authoritative immutable message. Callback bodies never carry message text or evidence content.

Every callback and signed evidence-export notification carries exactly these seven lowercase logical headers:

| Header | Required value |
|---|---|
| `content-digest` | `sha-256=:` + base64 SHA-256 of the exact request bytes + `:` |
| `x-pc-event-id` | Stable safe identifier (`A-Z`, `a-z`, digits, `.`, `_`, `:`, `-`) |
| `x-pc-event-timestamp` | Canonical UTC ISO 8601 instant; it must round-trip through `toISOString()` unchanged |
| `x-pc-key-id` | Safe identifier of the Ed25519 verification key |
| `x-pc-schema-version` | Exact body schema constant: `case-event-v2` for callbacks or `evidence-export-notification-v1` for export notifications |
| `x-pc-signature-version` | Constant `pc-webhook-ed25519-v1` |
| `x-pc-signature` | `ed25519=:` + base64 64-byte Ed25519 signature + `:` |

Header names are case-insensitive on the wire. Canonicalization uses the uppercase method, exact path including query, and this newline-delimited input with no trailing newline:

```text
@method: POST
@target-path: /exact/path?exact=query
x-pc-event-id: <event-id>
x-pc-event-timestamp: <canonical-timestamp>
x-pc-key-id: <key-id>
x-pc-schema-version: <schema-version>
content-digest: <digest-of-exact-body-bytes>
```

The signature-version header selects this canonicalization contract; it and the signature itself are not lines in the signed input. The signature binds the HTTP method, exact path and query, event ID, event timestamp, key ID, schema version, and exact-body SHA-256 digest. Retries retain `eventId`, `deliveryId`, `sequence`, and occurrence time but receive a fresh signing timestamp and signature. Partner-requested replay retains `eventId` and `sequence` while creating a new `deliveryId`.

Ordinary REST mutations use `Idempotency-Key`. Signed evidence-export notifications are the exception: they omit that header and deduplicate by the authenticated exporter plus signed event identity, while rejecting a reused identity with conflicting content.

## Ordering and retries

**Partner platforms: requires a partner key** with the `platform_orchestrator` role and `callbacks:manage` to inspect or replay deliveries. All results are scoped to that partner.

`sequence` is monotonic per partner when an event enters the durable outbox. Delivery completion can occur out of order because endpoints, retries, and overlap deliveries are independent. Consumers should persist the highest observed sequence, tolerate gaps, and fetch authoritative resources; they must not delay acknowledgement waiting for a missing sequence.

Retryable transport failures use exponential backoff and enter `dead_letter` after eight failed attempts. Inspect with `GET /api/webhooks/deliveries`, then use the idempotent partner replay endpoint after correcting the receiver. Automatic and partner-requested deliveries are both at least once.

## Rotation overlap

**Partner platforms: requires a partner key** with the `platform_orchestrator` role and `callbacks:manage` to register, rotate or manage endpoints. Account OAuth cannot manage partner callbacks.

`POST /api/webhooks/endpoints` can replace the active endpoint with a 60–86400 second overlap. During overlap, both active and retiring endpoints receive the same event ID and sequence with different delivery IDs. Signing-key rotation likewise publishes one active and one retiring public key; retain the retiring key through its expiry.

Endpoint registration resolves the hostname and rejects loopback, private, link-local, metadata, failed-DNS, and mixed public/private answers. Delivery resolves again and pins the vetted addresses, refuses redirects, and therefore fails closed if DNS changes.
