Integrate UniPost Inbox into your app
Map each authenticated app user to one stable external_user_id, keep the workspace API key behind your backend, and use an explicit Inbox scope for every read, write, and real-time connection.
The browser must not receive the UniPost API key. It authenticates only to your app; your backend derives the selected UniPost scope from that verified app session.
What you will build
- Connect each app user's social account with the same stable app-owned identity used for Inbox access.
- Proxy managed-user reads and writes through one server-side boundary.
- Relay scoped WebSocket events through your app's own authenticated channel.
- Keep owner/admin aggregation on a separate route with a second authorization check.
- Prove user A and user B cannot cross scope before production rollout.
Prerequisites
- An Inbox-eligible workspace plan.
- A workspace API key stored as a backend secret, never in client code or browser storage.
- A UniPost profile that will own the managed social accounts.
- Authenticated app users with stable internal IDs and explicit app owner/admin roles.
1. Choose one external user ID
Use an opaque, immutable app user ID such as app_usr_7f4c91. Derive it from the authenticated app user on your server. Email is mutable and should not be the primary identity; pass it only as optional reconciliation data. Never reuse one external_user_id for multiple app users.
The same value must be used when the user connects an account and whenever your backend requests that user's Inbox. This identity continuity is the boundary that associates social accounts, comments, and DMs with the intended managed user.
2. Connect the user's account
Call POST /v1/connect/sessions from your backend, then redirect the browser only to the returned hosted URL. The example uses app_usr_7f4c91 from the verified app session rather than a browser-supplied request field.
An ownership conflict fails closed. If the provider account is already associated with another external_user_id, hosted Connect returns HTTP 409 and says the social account cannot be reassigned. Resolve the account ownership issue; do not change IDs or silently move the account.
3. Build the managed-user backend boundary
Authenticate the app request first, then pass the resulting user object into a server-only helper. Do not expose inbox_scope or external_user_id as unrestricted browser parameters. Every managed-user request must resolve to inbox_scope=managed_user and the app-derived identity.
4. Read one user's Inbox
Read the list with GET/v1/inbox, get the badge count from GET /v1/inbox/unread-count, and fetch a single item with GET /v1/inbox/{id}. The list limit defaults to 50 and caps at 500; it is a bounded result size, not cursor pagination or an unbounded history export.
Treat a scoped 404 as unavailable. Do not attempt to distinguish a missing ID from an item owned by a different managed user; that indistinguishability is part of the isolation contract.
5. Perform scoped writes
Use POST /v1/inbox/{id}/read, POST /v1/inbox/mark-all-read,POST/v1/inbox/{id}/reply, and POST /v1/inbox/{id}/thread-state through the same helper. UniPost rechecks the selected scope for each item ID; cross-scope item operations return 404.
For an X write with an uncertain outcome, retry with the same idempotency key and payload. Never generate a new key just because the network response was lost.
6. Sync the selected scope
POST/v1/inbox/sync without x_backfill runs the ordinary selected-scope polling path. It is not the same operation as metered X history lookup.
Add x_backfill only when you intend to read bounded X history. Managed-X reads can consume X Credits. Inspect estimated_x_credits; when confirmation_required is true, repeat the exact scope, account, and request with the short-lived confirmation_token. X DMs remain controlled by the x_dms_v1 workspace rollout; do not request include_dms unless the capability is available.
7. Relay real-time events from your backend
Native browser WebSocket cannot set the required API-key Authorization header. Open GET /v1/inbox/wsfrom a trusted backend process, then relay only the allowed event to the app user's existing authenticated WebSocket or SSE channel.
Never put a UniPost API key in the WebSocket URL. After a disconnect, reconnect with bounded backoff and refreshGET/v1/inbox plus GET /v1/inbox/unread-count; real-time delivery is a notification path, while the HTTP reads remain authoritative.
8. Add a separate owner/admin aggregate
Check the role in your own app before calling UniPost. Then use inbox_scope=workspace without external_user_id. The workspace API key must be creator-bound, and its creator must still hold the UniPost owner or admin role. Keep this route separate from every managed-user handler.
9. Handle errors without weakening scope
Authentication and explicit Inbox scope resolution run before the plan gate, then the selected endpoint runs. A malformed scope can therefore return before a possible 402. Never fall back from managed-user scope to workspace scope after an error.
| Status | Meaning | App behavior |
|---|---|---|
400 | Missing, duplicate, invalid, or disallowed scope fields. | Fix the server request; do not retry unchanged. |
401 | Invalid, missing, or inactive credentials. | Stop and replace or reactivate the credential. |
402 | The workspace plan does not allow Inbox. | Show an upgrade path; do not retry automatically. |
403 | Insufficient role, creatorless aggregate key, or controlled feature denial. | Correct authorization or feature eligibility. |
404 | Managed user missing, item missing, or item outside the selected scope. | Return unavailable without disclosing ownership. |
409 | Connect ownership conflict or a conflicting durable operation state. | Resolve ownership or inspect the operation; never reassign silently. |
500 | INBOX_SCOPE_LOOKUP_FAILED is a transient pre-handler lookup failure. | Retry the same request with bounded exponential backoff. |
The narrow retry guidance above applies to INBOX_SCOPE_LOOKUP_FAILED because the selected endpoint did not run. Do not assume every 5xx is safe to retry after a write.
10. Production security checklist
- Store the API key only in the backend secret manager and redact it from logs.
- Verify the app session before deriving
external_user_id. - Keep managed-user handlers and owner/admin aggregate handlers separate.
- Let UniPost enforce selected scope again for item, reply, and thread IDs.
- Relay real-time events only to the app channel authorized for the matching external user.
- Exclude private DM bodies, participants, access tokens, and raw provider errors from logs and analytics.
- Fail closed; never change to workspace scope because a managed-user lookup failed.
11. Prove A/B isolation before production
Use only app-owned test identities: synthetic user A and synthetic user B. Do not run this acceptance against a customer account. Create controlled Inbox fixtures for each identity, verify every assertion, then remove all synthetic fixtures and confirm residual counts are zero.