Overview
The in-app system lets you deliver real-time, wallet-targeted notifications to your users without requiring email or phone numbers. It consists of:- A dashboard configuration surface to manage allowed browser origins and publishable keys.
- A public two-step wallet authentication flow (challenge → verify) using a publishable key (
pk_*). - A Socket.IO gateway that streams pushes to connected clients and replays pending pushes on reconnect.
- A server-to-server API to send pushes using a secret key (
sk_*).
Concepts
Keys
- Publishable key (
pk_*): safe to use in the browser. Used only for the challenge/verify flow. - Secret key (
sk_*): server-to-server only. Used to send pushes programmatically.
Allowed origins
The in-app runtime requires anOrigin match against your allowed origin list.
pk_live_*keys map to production origin entries.pk_test_*keys map to development origin entries.
Dashboard setup (Integrations)
These endpoints are intended for your internal dashboard UI (cookie session auth + org role).1) Check in-app integration status
GET /integrations/inapp/status
Returns:
- publishable keys
- available secret keys
- currently connected SDK sessions
- daily usage limit and usage
- allowed origin count
2) Allow your frontend origin
POST /integrations/inapp/origins
Body:
3) Send a test push (optional)
POST /integrations/inapp/test-push
Body:
Browser SDK auth flow (challenge → verify)
The browser flow uses a publishable key (pk_*) plus an origin allowlist check.
Step 1 — Request a challenge message
POST /inapp/challenge
Headers:
x-publishable-key: pk_live_...(orAuthorization: Bearer pk_live_...)Origin: https://app.example.com
Step 2 — Sign and verify
The wallet must sign the returnedmessage (EIP-191 signMessage) and send it to /inapp/verify.
POST /inapp/verify
Headers:
x-publishable-key: pk_live_...(orAuthorization: Bearer pk_live_...)Origin: https://app.example.com
Example: signing with ethers
Real-time delivery via Socket.IO
After verification, connect a Socket.IO client to thewsUrl returned by /inapp/verify.
Socket.IO path:
/api/v1/inapp/register
handshake.auth.token = <session jwt>- or
Authorization: Bearer <session jwt> - or query param
?t=<session jwt>
Example: connect and listen for pushes
Client events
REGISTER(optional but recommended): confirms the wallet for the connectionEVENT: records interaction events (delivered,viewed,dismissed,clicked)
Sending pushes (server-to-server)
Use this when you want maximum control from your own backend (or an external relay).POST /inapp/push
Auth:
Authorization: Bearer sk_...(orx-secret-key: sk_...)
Automations integration (sending in-app from workflows)
Automations can send in-app pushes to one or many wallets. Internally the system:- creates a
CampaignRunfor tracking - writes a
DeliveryEventrow for analytics (sent, plus interaction events) - stores a short-lived pending list per wallet in cache (replayed on reconnect)
Limits, TTLs, and environment variables
Push TTL
- Pushes expire after
ttlHours(clamped to 1..72 hours, default 72). - Expired/dismissed/clicked pushes are pruned and won’t be replayed.
- Per-wallet pending list is capped to the 10 most recent pushes.
Daily quota
INAPP_DAILY_LIMIT(default50000) limits pushes per org per UTC day (429 if exceeded).
Session token TTL and secrets
INAPP_SESSION_SECRETmust be set (fallbacks:JWT_ACCESS_SECRET,BETTER_AUTH_SECRET)INAPP_SESSION_TTL_SECONDScontrols session expiry (clamped to 10 minutes..30 days; default 7 days)
Troubleshooting
- If
/inapp/challengeor/inapp/verifyreturnsOrigin not allowed:- confirm the browser’s
Originmatches the configured allowed origin exactly (scheme + host + port) - confirm you used the correct publishable key (
pk_live_*vspk_test_*)
- confirm the browser’s
- If pushes are not delivered live:
- confirm the client is connected to Socket.IO and listening for
PUSH deliveredNow: falsemeans the wallet was offline; the push will be replayed on reconnect until it expires
- confirm the client is connected to Socket.IO and listening for

