OOctwin Platform In-app chat· Published
In-app chat

Guide · in-app chat

Your AI Agent, inside your app

Put the same chat your customers use on your website inside your iOS or Android app. When they close the app with a reply still unread, your server is told — and sends the push to their phone.

Operators · sections 1–3 App developers · sections 4–5 Server developers · section 6

1 · The shape of it

One chat, now in your app too

Your app shows the chat in a WebView, with your customer already signed in. Everything works as on your website: the AI Agent, buttons and lists, photos and voice notes, and a person from your team taking over in Live chat.

What an app needs and a website does not is a way to reach the customer when the app is closed. So when a signed-in customer has not seen a message for a few seconds, octwin.ai sends your server a signed alert, and your server sends the push through the Firebase or Apple setup it already has. We never hold your push keys or your customers’ device tokens.

WhoDecides
The chat in your appWhether the customer has seen a message.
octwin.aiWhen a message has waited unseen long enough to alert.
Your serverHow to reach the phone — and whether to at all.

The same customer, one conversation. A customer who chats on your website and later in your app is one contact with one history, as long as your server signs them in with the same id on both.

2 · When an alert goes out

The 15-second rule

Every message starts a short timer. If the customer sees it, the timer stops. If they do not, your server hears about it.

10:00:00  Your team replies to customer user-123   → a 15-second timer starts

A) they are looking at the chat
10:00:02  the chat reports “seen”   → the timer stops, no alert

B) the app is closed, in the background, or on another screen
10:00:15  nothing was reported   → octwin.ai alerts your server → your server sends the push

Seen means the chat is open, the app is in front, and it has been on screen for a moment. The delay is yours to choose, from 5 to 300 seconds. Messages that arrive while an alert waits join it — the alert counts them and quotes the newest — and a customer gets at most one alert a minute.

Why not just check whether the chat is connected? Because a phone keeps a connection open after the app goes to the background, and sometimes keeps one that has quietly died. “Connected” does not mean “looking”, and a check built on it fails without anyone noticing: no push, a missed reply. This rule fails the other way — when in doubt, it alerts. The worst case is one push too many.

3 · For operators

Turn it on

Early access. In-app chat is part of the Pro and Enterprise plans and is in early access — ask us to turn it on for your workspace.

  1. Add a separate web widget for your app (Channels → add a web widget), so its settings and its alerts are the app’s own.
  2. Turn on Identity verification on it and give the secret to your server developer. Only signed-in customers can be alerted.
  3. Open In-app chat on the widget. Tick App page and give its address to your app developer.
  4. Tick Send alerts, enter your server’s address, choose the delay and whether the message text rides in the alert, and save.
  5. Give the alert secret to your server developer, then press Send test. The answer tells you whether your server accepted it.
The In-app chat panel on a web widget: the App page address, the alert settings with Save and Send test, the alert secret, a receiver example, and a list of recent alerts
The In-app chat panel. Recent alerts lists every alert: delivered, cancelled (with why — seen is the feature working), or failed.

Choose “No message text” if your customers would not want the words of a message on their lock screen. The alert then says only that a message is waiting.

4 · For app developers

Put the chat in your app

Open one address in a WebView. Your server gives you that address with your customer already signed in.

https://octwin.ai/c/app/YOUR_WIDGET_ID#from=FROM&identity=IDENTITY&identity_sig=SIGNATURE

Everything after # stays in the WebView: it is never sent to a server, so the signature lands in no log. The page reads it and removes it from the address. Optional extras: &name= for the customer’s display name, and &active=0 to open the page while the chat is not yet on screen.

Tell the chat when it leaves the screen — required

A WebView cannot always tell that your app went to the background, and a chat that believes it is on screen reports messages as seen — so no push goes out. Call:

window.octwin.setActive(false)   // the app went to the background, or the customer left the chat screen
window.octwin.setActive(true)    // the chat is on screen again

What the page tells your app

The page posts a JSON string to your app: {"source":"octwin","type":"ready"} once the chat is loaded, and {"source":"octwin","type":"identity_refused"} when a signature has expired. Then ask your server for a fresh one and hand it over without a reload:

window.octwin.setIdentity({ from, identity, identity_sig })

Already have your own page? Load /widget.js on it and call PlatformChat.mount(element, { tenantSlug, projectSlug, widgetId, from, identity, identitySig }). It returns the same setActive, and an onIdentityRefused option tells you when to re-sign.

5 · Per platform

The checklist for each platform

Working samples for all four live in one public repository — github.com/Cequens/octwin-in-app-chat — with a sample server beside them.

PlatformThe bridgeOff screenSample
Android WebViewCompat.addWebMessageListener, named OctwinBridge onPause — and call webView.onPause() too android/
iOS A WKScriptMessageHandler named OctwinBridge sceneWillResignActive — not later ios/
React Native react-native-webview’s onMessage AppState → background react-native/
Flutter A JavaScriptChannel named OctwinBridge didChangeAppLifecycleState → paused flutter/

6 · For server developers

Your server: sign in, and receive the alert

Sign your customer in

Sign a small JSON text with the widget’s identity secret and build the page address. Sign the exact text you send, and encode each value with encodeURIComponent — not URLSearchParams, which writes a space as + and would break a phone number like +20….

const identity = JSON.stringify({ from: `user-${user.id}`, phone: user.phone, exp: now + 3600 })
const sig = crypto.createHmac('sha256', IDENTITY_SECRET).update(identity).digest('base64url')
const url = `https://octwin.ai/c/app/${WIDGET_ID}#from=${encodeURIComponent(`user-${user.id}`)}` +
  `&identity=${encodeURIComponent(identity)}&identity_sig=${encodeURIComponent(sig)}`

Receive the alert

Each alert is one POST in the Standard Webhooks format, which has official libraries in nine languages. Verify it with the alert secret over the raw body, answer quickly, then send the push.

import { Webhook } from 'standardwebhooks'
const wh = new Webhook(ALERT_SECRET)

app.post('/octwin/unread', express.raw({ type: 'application/json' }), (req, res) => {
  const alert = wh.verify(req.body, req.headers)   // throws when the signature is wrong
  res.sendStatus(204)                              // answer first
  sendPush(alert.data.visitor.from, alert.data.preview, alert.data.conversation_id)
})
{ "type": "message.unread",
  "data": { "visitor": { "from": "user-123", "claims": { } },
            "conversation_id": "…", "unread_count": 2,
            "preview": "Your order has shipped", "author": "person" } }

7 · When it does not work

The five things that happen

What you seeWhyWhat to do
No push when the app is closed The app never calls setActive(false), so the chat counts messages as seen. Wire the off-screen call for your platform (section 5).
Two pushes for one message Your server handled a retry as a new alert. Dedupe on webhook-id, and answer within 5 seconds.
The chat forgets who the customer is The signature expired, or the identity secret was rotated. On identity_refused, fetch a fresh signature and call setIdentity.
unread_alert_rejected on the widget Your server answered 401 or 403 — usually the alert secret does not match. Copy the alert secret again, then press Send test.
Send test fails The address is wrong, is not public HTTPS, or the server answers slowly. Read the answer under the button; it names the status or the error.

Check it on a real phone before going live: put the app in the background, have your team reply in Live chat — one push should arrive. Tap it — the chat should open on that reply. With the chat open and in front — no push.

8 · Words used above

Glossary

App page
The page octwin.ai hosts for a widget, for your app to open in a WebView.
from
Your id for the customer in the chat, such as user-123. The alert names it, so your server knows whose phone to reach.
Identity secret
What your server signs the customer in with. Server-side only.
Alert secret
What your server checks each alert with. Server-side only; a separate key from the identity secret.
Seen
The chat was open, in front, and on screen for a moment.
Unread alert
The message.unread POST your server receives when a message stays unseen.