Graine AI

Calls Graine rings: booked calls and Ring now

Let Graine ring your users' phones like WhatsApp for the calls the agent, the user or your team booked: create the APNs key and the Firebase service account click by click, upload them, hand the SDK its push tokens and pushes, and test with Ring now.

When a booked call is due, Graine rings the user's newest phone through your own push credentials: the system's call screen on the lock screen (CallKit on iPhone), a full-screen ring on Android. The user answers, and the agent opens knowing it is the booked call and what it is about — it hears who_called = scheduled and the booking's reason as call_reason. The ring itself carries no reason and nothing about the user.

One booked call on the call screen: Android ringing full screen with Decline and Answer; the call on an iPhone with the timer and the line being said; the call ended; a missed call with Close and Call back

What it needs in the dashboard

WhereSettingValue
Workspace settings → In-app calling (an Admin)iOS: Bundle ID, Team ID, Key ID, the .p8 file, EnvironmentAutomatic (each device's own) unless you know otherwise. Wait for Checked and working.
Workspace settings → In-app callingAndroid: Package name, Service-account key (.json file)Wait for Checked and working.
Agent → Calls & scheduling → How it ringsName on the incoming call, Ring for, After a missed call, ring once more after, Ring each user at most, Quiet hoursWhat the phone shows and how persistent the ring is
Agent → Calling WindowThe hours the agent may ringEvery ring falls inside it, in the user's own time zone
Agent → Memory → SettingsWho is the user?Rings go to the identified user's devices
Agent → Calls & schedulingIn-app call audioWebRTC (recommended) or Standard: with the SDK's WebRTC module in your app (0.9.0), an answered ring carries its audio over WebRTC — Calls over WebRTC

Create your credentials

You make these once per app, in your own Apple and Google accounts; you upload only the key file and its details.

iOS — an APNs key. You need an Apple Developer Program membership (Account Holder or Admin) and your app's bundle ID.

  1. developer.apple.com/account → Certificates, Identifiers & Profiles → Keys → +.
  2. Name it ("Graine calls"), tick Apple Push Notifications service (APNs); if asked, Sandbox & Production and Team Scoped (All Topics).
  3. Continue → Register → Download. The .p8 downloads once only. Note the Key ID (10 characters).
  4. Your Team ID: the top right of your developer account, or Membership details.
  5. Identifiers → your bundle ID → Push Notifications enabled. In Xcode, the app target → Signing & Capabilities → Push Notifications, and Background Modes with Voice over IP and Audio, AirPlay, and Picture in Picture.
  6. In Graine: Workspace settings → In-app calling → iOS → Set up: Bundle ID, Team ID, Key ID, the .p8 file, Environment Automatic (each device's own).

Android — a Firebase service account. You need a Firebase project that contains your Android app.

  1. console.firebase.google.com → your project → Add app → Android → your package name → download google-services.json into app/; add Firebase Cloud Messaging to the app (below) — the SDK registers for pushes by itself.
  2. Project settings → Cloud Messaging → Firebase Cloud Messaging API (V1) shows Enabled.
  3. Project settings → Service accounts → Firebase Admin SDK → Generate new private key → Generate key. A .json downloads; treat it like a password.
  4. In Graine: Workspace settings → In-app calling → Android → Set up: the package name and the .json file.

Never put the .p8 or the service-account .json in your app, a repository or an email. Upload them in the dashboard, where they are encrypted with your workspace's own key and never shown again.

Every detail, with screenshots: Hosted ringing → Create your credentials.

What to do, in order

  1. Upload the credentials (above) and wait for Checked and working.
  2. Hand the SDK its push token and every push: iOS registers its VoIP token itself once you enable VoIP pushes at launch; Android apps forward Firebase's token and messages in two lines.
  3. Identify the user — rings go to the identified user's devices seen in the last 30 days, newest first.
  4. Show the call screen while your app is open (as for calls the user places).
  5. Book a call — the agent in a conversation, the user from your app (the booking sheet), or your team — or test at once: Agent → Calls & scheduling → Ring a user now → the user's identifier → Ring now. → The phone rings within seconds; the dashboard shows what became of it.
  6. Hear the ring and its outcome: answered, declined (nothing rings again for 6 hours), missed (one more ring after your delay, if set), failed.

Answering over WebRTC. With the SDK's WebRTC module in the app, the SDK prepares the call while the ring is showing, so answering connects at once — over WebRTC, or, when WebRTC cannot set it up, over the WebSocket inside the same call. A declined, missed or expired ring lets go of what it prepared; nothing was dialled. Calls over WebRTC

The code, platform by platform

2. At every launch — iOS hands over a VoIP push only once this has run:

final class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication,
                     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
        let graine = GraineInApp.configure(GraineConfig(baseUrl: "https://www.graine.ai", publishableKey: "pk_live_…"))
        graine.calls.configure(GraineCallsConfig(displayName: "Asha from Acme"))
        graine.calls.enableVoIPPushes()          // registers this iPhone with Graine for rings
        return true
    }
}

3. GraineInApp.shared.identify(["user_id": user.id]) after sign-in.

4. The call screen while the app is open — the lock screen uses the system's:

struct RootView: View {
    @ObservedObject private var calls = GraineInApp.shared.calls
    var body: some View {
        Text("Your app").fullScreenCover(isPresented: .constant(calls.showsCallScreen)) { GraineCallView() }
    }
}

6. Hear the ring and the outcome:

final class Rings: GraineCallsListener {
    func onIncomingCall(_ call: GraineIncomingCall) {
        print("Ringing: \(call.callerName ?? "the agent"), booked by Graine: \(call.hosted)")
    }
    func onCallOutcome(_ outcome: GraineCallOutcome, callId: String) {
        print("\(callId): \(outcome.wire)")       // answered, declined, missed, ended, failed
    }
}

A missed ring: GraineCallView shows Missed call with Close and Call back for 30 seconds; for a screen of your own, calls.missedCall, calls.callBack() and calls.dismissMissedCall().

Troubleshooting

What you seeWhyFix
Ring now says No deviceThe user has no app device seen in 30 days, or this device is not linked to themSign in to the app on the phone (identify), open it once, try again
The credentials show a problemA wrong Key ID, Team ID or bundle ID, or a revoked keyReplace them in Workspace settings → In-app calling
It rings only with the app open (Android)Notifications are refused, or the app's own messaging service does not forward its messagesAsk for notifications; an app with its own service calls GraineCalls.handlePush first in onMessageReceived
It never rings on iPhoneenableVoIPPushes not called at launch, Push Notifications or Voice over IP missing, or a simulatorFix the capabilities; test on a real iPhone
It rang later than bookedOutside the user's calling window or inside quiet hoursScheduled calls → What happens at the time

In depth: Graine rings your app · Scheduled calls.

On this page