Going live
The in-app agent from the dashboard to a monitored launch — every setting, every step in the app, how to prove it works, and what each failure means.
One path, in order. Each step links to the page that covers it in full.
1. The dashboard
| Setting | Where | Why |
|---|---|---|
| An agent for this surface | Agents | An app's agent has different tools and a different prompt from your phone agent — one agent per surface |
| The publishable key | Agent → Embed & Widgets | pk_live_…. Public by design: it names the agent and cannot place a call on its own |
| Allowed domains | Agent → Embed & Widgets | app:// for a native app (an app sends no web address, and this entry is how it is let in); your own domains for a website. Never Graine's own domain — anyone could then use your key |
| Call audio | Agent → Embed & Widgets → Appearance → Setup | High quality (WebRTC) for Opus, a jitter buffer and the device's echo canceller. It needs WebRTC enabled for your organisation — ask Graine; a relay for mobile networks is included. If WebRTC cannot connect, calls move to the standard path by themselves |
| The look | Agent → Embed & Widgets → Appearance | A design from the Garage, your logo (drawn whole, on your brand colour or white) and your loader's words — Your design |
| App actions | Agent → Embed & Widgets | Every action your app registers must be declared here, or the agent cannot call it — Actions |
| Components | Agent → Embed & Widgets, then picked per agent | The cards, lists and choices the agent may show — Components → Turning one on |
2. The app
| Step | React Native | Android | iOS | Flutter | Website |
|---|---|---|---|---|---|
| Install | @graineai/inapp-react-native 0.41.0 or later; for calls, @graineai/inapp-react-native-calls 0.2.0 | ai.graine:inapp-android:0.7.0 | inapp-ios from 0.7.0 | graine_inapp: ^0.7.0 | the loader, or your own page |
| Microphone | Declare RECORD_AUDIO and MODIFY_AUDIO_SETTINGS (Android) and NSMicrophoneUsageDescription (iOS) — with Expo, in app.json. The SDK asks on Android when a call starts; iOS asks the first time the call uses it | In the library's manifest. The SDK asks at start(); forward onRequestPermissionsResult to it | NSMicrophoneUsageDescription. The SDK asks at start() | Android manifest and Info.plist; your app asks, through requestMicrophone in GraineConfig | The frame's allow="microphone" |
| Camera purpose string (with WebRTC) | NSCameraUsageDescription under ios.infoPlist (webrtc: true) | — | NSCameraUsageDescription with GraineInAppRTC | NSCameraUsageDescription in Info.plist with GRAINE_WEBRTC | — |
| Engine loaded early | launcher mounted at start with active={false} | voice.attach(activity) in your first activity | the bar, or voice.attach() | GraineEngine around the navigator | idle=1, then graine:warm on graine:ready |
Then, on every platform:
- Who the customer is —
identify()with the signed-in customer, so the agent greets them by name. Screen context → Who the customer is - What is on screen — one screen name per screen, the fields and their status. Screen context
- What it may do — one handler per declared action; answer within six seconds and say what actually happened. Actions
- The call UI — the SDK's bar, or your own with every state drawn.
3. Prove it works
Place one test call per platform and open it from Call History → the call → Open full logs (Execution Logs). Under What happened in the app you should see:
| Row | Means |
|---|---|
transport: WebRTC | The call ran on WebRTC. fell back to WebSocket (reason) names why not — see below |
connect: warm · 0.6s (setup 0.1s, answer 0.5s) | How long the tap took. warm means the engine had registered ahead of the tap; a cold connect on every call means the engine is not loaded early |
recovery: … | The engine replaced a line the network had dropped, or placed the call again on a fresh connection — and still connected |
connection quality: … | The line got worse or better during the call, with the numbers |
| The screen, and your action | The agent saw the screen you were on, and called your action with the answer you returned |
Then try the four things customers do: deny the microphone, lose the network mid-call, call from mobile data, and hang up from both sides. The dashboard's Playground checks the rest from your desk: the key, that an unlisted site is refused, and that the agent can call its components.
4. Watch it in production
- Execution Logs, per call, as above.
- Your own analytics: the launcher says where every connect is
(
connectStage,slowConnect,callFailed), and counting them shows how calls connect on your customers' real networks — see Voice → While it connects. onTransporton every SDK: a fallback is not a failure, but anot_configuredone never fixes itself. Send it to your error reporting.
When something is wrong
| What you see | Cause | Fix |
|---|---|---|
Every session refused in an app, no_origin | app:// is not in Allowed domains | Add it — Voice → When rtc cannot be used |
A website always falls back, not_configured | Your page did not vouch for its site | Use the loader, or answer graine:need-ticket — Your own UI → Website. Do not add Graine's domain |
| "Connecting…" for long on mobile data | A line the network dropped, being replaced — or the engine is not loaded early | Look for recovery and cold rows; mount the engine at launch |
| The call answers and nobody hears anything | The network blocks the media path | The engine moves the call within seconds (reason: media); if it is every call, ask Graine to check the relay |
| The agent cannot see the screen | No screen named, or the screen channel never joined (screen_channel) | Screen context; the engine restarts such a call on the standard path |
| "The agent is not configured to call" an action | It is registered in the app but not declared | Declare it under Embed & Widgets |
| The design or typeface did not change | The app draws its own bar, or an older SDK | Your design → When the phone does not match |
| The logo is cut off | An SDK older than React Native 0.40.0 / native 0.5.0 | Update; choose the logo's background under the upload |
| The agent greets with a blank name | identify() not called before the call | Screen context → Who the customer is |
| The microphone is refused | The customer said no, the permission is not declared, or (Flutter) requestMicrophone is not set | Declare it — the usage string on iOS, RECORD_AUDIO on Android — and draw the refusal with the way to Settings: the SDK does not ask again by itself |
| The call ends when the screen locks or the app is put away | The assistant bar's call (voice) ends on purpose when the app leaves the screen (endOnBackground) | For calls that go on with the screen off, use the native calls — calls on iOS and Android, @graineai/inapp-react-native-calls on React Native, GraineCalls on Flutter — see In-app calling |
| A call push does not ring | iOS: not a VoIP push, the wrong APNs host, or enableVoIPPushes not called at launch. Android: a notification block in the FCM message, or handlePush not called | In-app calling → Troubleshooting |

