Mobile Integration

Flow doesn't currently have mobile-specific SDKs, but with our hosted verification system, verifying users with an embedded WebView inside of your native application is extremely easy.

Hosted verifications are Cognito-hosted URLs that contain your full verification experience. These hosted verifications can be generated via API and then the URLs for these verifications can be presented by your mobile app. Via API, you’re able to send a request from your backend that looks approximately like this:

POST /flow_sessions?idempotent=true HTTP/1.1

  "shareable": true,
  "template_id": "flwtmp_11111111111111", // Replace with your template ID
  "user": {
    // Read this for more context:
    "customer_reference": "your-users-internal-database-id-here",

    // Optional, but useful for fraud signals:
    "email": ""

We’ve omitted the other required headers from that example for brevity. You can read about our authentication system on our docs.

One additional note about the query parameter:

You can optionally supply ?idempotent=true for this specific request. With idempotency enabled, we will respond with a 201 Created HTTP status code the first time you send us the associated (customer_reference, template_id) pair. After that, you can always send the same (customer_reference, template_id) pair again and we will not create a new session, instead returning a 200 OK status code with the previously created session resource in the body.

In general, Flow is designed so that everything can be configured and managed without code changes. Part of this includes not forcing you to persist the flwses_111... IDs we return, instead using your internal identifier. We provide this idempotency feature so that you can, for example, have your backend do POST /flow_sessions?idempotent=true every time a user enters your KYC flow on mobile, without having to worry about either doing a POST or GET depending on their past interactions.

The above request returns a full FlowSession resource that will include a shareable_url entry:

  "id": "flwses_42424242424242",
  "customer_reference": "your-users-internal-database-id-here",
  "shareable_url": "",
  "template": {
    "id": "flwtmp_11111111111111",
    "version": 7

  // Trimmed for brevity
  // Full docs on this response payload here:

That shareable_url is unique to this session and should only be opened by the customer associated with the customer_reference and email you provided.

For your mobile application, you can then embed this shareable_url in a WebView and everything should just work from there. Once you have it configured via our dashboard, you’ll receive webhooks as the user completes each step of their session, ending with a webhook letting you know if they passed or failed the session.

The implementation within your app should be fairly straightforward, but one additional note just to help make sure your integration is secure:

Within your app, when your user clicks the UI element for verifying their identity, your application should either POST or GET to an internal API of yours without any payload in the body. Meaning that the customer_reference and email you provide from your backend to our API should not be controllable by the end user. This is important to the product’s security model, since we use the (customer_reference, template_id) pair’s uniqueness to enforce that a user can only make one verification attempt until you manually authorize a retry.

Native Events

If you are embedding hosted Flows inside of a webview, you can attach a WKUserContentController to a WKWebView to receive events. A bare bones implementation might look like this:

import SwiftUI
import WebKit

struct WebView : UIViewRepresentable {
  let request: URLRequest
  let contentController = ContentController()

  func makeUIView(context: Context) -> WKWebView  {
    let webView = WKWebView()
    return webView

  func updateUIView(_ uiView: WKWebView, context: Context) {
    uiView.configuration.userContentController.add(contentController, name: "cognito")
    uiView.configuration.allowsInlineMediaPlayback = true

  class ContentController: NSObject, WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
      print("Received a Flow event")

struct ContentView: View {
  @State var showSafariView = false
  let flowUrl: URL

  @ViewBuilder var body: some View {
    Button("Launch flow", action: { showSafariView = true }).sheet(isPresented: $showSafariView) {
      WebView(request: URLRequest(url: flowUrl))