Connect sync and domain services to app entitlements
Use this contract when a separately deployed sync or domain service must enforce the same app rights as the platform. The service can introspect online, maintain a local projection, or validate a short-lived signed grant. Raw payment, identity, and provider evidence remains on the server.
Choose an integration mode
| Mode | Best for | Contract |
|---|---|---|
| Native resolver | Code in the same Workspace process | Direct tenant-isolated domain call |
| Introspection | One online decision | Current state for one app actor |
| Snapshot and change feed | A local service projection | Full initial state followed by ordered changes |
| Ed25519 grant | Short offline decision | Audience-bound, short-lived signed evidence |
| Webhook | Low-latency notification | At-least-once delivery; the feed remains the reconciliation contract |
For a standalone sync service, use snapshot and change feed as the default. Add webhooks for faster notification, not as a replacement for reconciliation.
Authenticate the service
An administrator creates a tenant- and app-bound service binding with an audience and the required capabilities, then rotates its credential. The secret is returned only when issued.
Send these headers with each authenticated request:
X-Nucleus-App-Service-CredentialX-Nucleus-App-Service-TimestampX-Nucleus-App-Service-NonceX-Nucleus-App-Service-Signature
The signature covers the HTTP method, full request URI including its query, timestamp, nonce, and the SHA-256 hash of the body bytes actually sent. A signed request body must not exceed 128 KiB. Generate a fresh cryptographic nonce for every attempt. Stale timestamps, invalid signatures, and nonce reuse fail closed.
For the remainder of the current major, the complete legacy set of X-Workspace-Service-Credential, X-Workspace-Service-Timestamp, X-Workspace-Service-Nonce, and X-Workspace-Service-Signature remains compatible. Send either the complete canonical set or the complete legacy set. Mixed and incomplete sets are rejected. New integrations use only the canonical headers.
Use the endpoints
| Method and path | Purpose |
|---|---|
GET /api/v1/public/v1/app-entitlements/service/introspect/{actorId} | Read one actor's current entitlement |
GET /api/v1/public/v1/app-entitlements/service/snapshot | Load the paged initial state |
GET /api/v1/public/v1/app-entitlements/service/changes?afterSequence=... | Continue with ordered changes |
POST /api/v1/public/v1/app-entitlements/service/grants/{actorId} | Issue a short-lived grant |
GET /api/v1/public/v1/app-entitlements/jwks | Load public Ed25519 verification keys |
Persist asOfSequence from the first snapshot page. Send both cursor and the same asOfSequence on every following page, then consume changes after that sequence in ascending order. If the feed returns ERR_APP_ENTITLEMENT_CURSOR_EXPIRED, discard the local projection and start with a new snapshot.
Validate grants and webhooks
Cache the JWKS and validate the signature, key ID, issuer, audience, nbf, and exp. The exp claim already uses the earlier boundary of the service binding's grant TTL and the effective entitlement's known validity. Treat valid_until as informational and never as a substitute for validating exp.
A valid token alone does not grant access. Also validate the state and the specific features or limits required by the operation. Never accept an expired grant or use it for another service binding.
Process webhooks durably before acknowledging them. Deduplicate by event ID and sequence. Delivery is at least once and may be delayed. Reconcile any gap through the change feed. The server permits only secured HTTPS targets and blocks private and loopback destinations; never place credentials in the target URL. During the compatibility window, webhooks contain both X-Nucleus-App-Service-Key and X-Workspace-Service-Key with the same value. New receivers use the canonical header and reject delivery if both values are present but differ.
Verify the integration
Test valid introspection, snapshot/feed continuation, the wrong audience, an expired grant, an entitlement that ends before the grant TTL, stale timestamps, nonce replay, duplicate webhook delivery, and cursor expiry followed by a fresh snapshot. Do not derive rights directly from a provider notification or store payload. The server remains the entitlement authority.