Sign in (OAuth 2.0)

Authenticate a customer inside an Apple Messages for Business conversation with Apple's native Sign in sheet and your OAuth 2.0 identity provider, then exchange the authorization code server-side.

Sometimes you need to know who you're talking to before you can help — look up an order, change an account setting, or share something private. Apple Messages for Business lets a customer authenticate inside the thread: you send a Sign in request, Apple presents a native sign-in sheet backed by your OAuth 2.0 identity provider, and you receive an authorization code to exchange server-side. The customer never leaves Messages.

This is the authenticate variant of an interactive message. It is sent as Apple's New Authentication (interactive data version 2.0).

Requirements

  • OAuth integration configured. Set up your OAuth 2.0 provider on the channel in the dashboard under Integrations → Apple Messages for Business → OAuth. A send fails fast if the integration is missing.
  • Device support. New Authentication (OAuth 2.0) requires iOS/iPadOS 16 or macOS 13 and advertises the AUTH2 capability token. Check the device's capability-list before sending; a device that doesn't advertise AUTH2 is rejected with 422 amb_capability_unavailable.
  • Your own IdP. The customer signs in against your authorization server — Blooio does not host an identity provider.

Send a Sign in request

Send an interactive message with kind: "authenticate". Put the OAuth 2.0 request under oauth2 (a shorthand for authenticate.oauth2); it is forwarded to Apple verbatim, so it uses Apple's field names.

curl -X POST https://api.blooio.com/v4/messages \
  -H "Authorization: Bearer bl_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 4d5e6f70-8a9b-4c1d-9e2f-3a4b5c6d7e8f" \
  -d '{
    "from": "support",
    "to": "opaque-amb-customer-id",
    "interactive": {
      "kind": "authenticate",
      "received_message": {
        "title": "Sign in to your account",
        "subtitle": "Verify it's you to continue",
        "style": "icon"
      },
      "reply_message": { "title": "Signed in", "style": "icon" },
      "oauth2": {
        "responseType": "code",
        "scope": ["openid", "profile", "email"],
        "redirectURI": "https://auth.yourbrand.com/amb/callback",
        "state": "order-10482-7f3a9c"
      }
    }
  }'
Try it

Fields

Field Type What it is
interactive.kind string Required. "authenticate".
oauth2.responseType string Required. The OAuth 2.0 response type — use "code" for the authorization-code flow.
oauth2.scope array of string Required. The scopes to request (e.g. openid, profile, email).
oauth2.redirectURI string (https) Required. Your registered redirect URI. Must match what your authorization server expects.
oauth2.state string Recommended. An opaque value you generate; echoed back so you can correlate the reply to this request and defend against CSRF.
received_message object The bubble the customer sees before signing in (title, subtitle, style, optional imageIdentifier).
reply_message object The bubble shown after they finish.

Note You may also nest the request under authenticate ("authenticate": { "oauth2": { … } }) instead of the flat oauth2 shorthand — both reach the wire. The request is validated for the minimum Apple needs (responseType, scope, redirectURI) before sending; a malformed request is rejected with 422 invalid_authenticate naming the missing field, rather than delivering an empty bubble.

The sign-in round trip: your send, Apple's sheet, the customer authenticating against your IdP, and the authorization code arriving back on an inbound webhook. Click a step for details.

What the customer sees

  1. A Sign in bubble appears with your received_message title/subtitle.
  2. Tapping it opens Apple's native authentication sheet, which loads your authorization server's sign-in page for the requested scope.
  3. The customer authenticates. Your server redirects to redirectURI with an authorization code (and your state).
  4. The sheet closes and the reply_message bubble is shown in the thread.

Reading the result

The outcome arrives as a normal inbound message webhook, with a structured metadata.interactive object carrying the authorization code and your state:

{
  "role": "reply",
  "kind": "authenticate",
  "code": "authorization-code-from-your-idp",
  "state": "order-10482-7f3a9c"
}

Then, server-side, exchange that code for tokens at your own token endpoint — exactly as in any OAuth 2.0 authorization-code flow:

curl -X POST https://auth.yourbrand.com/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=authorization-code-from-your-idp" \
  -d "redirect_uri=https://auth.yourbrand.com/amb/callback" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET"

Verify the returned state matches the one you sent before trusting the code. The access token your IdP returns identifies the customer — use it to look up their account and continue the conversation with confidence.

Note The code is single-use and short-lived. Exchange it promptly, and never do the exchange from a client — keep your client_secret server-side.

Troubleshooting

Symptom Cause & fix
422 invalid_authenticate The oauth2 object is missing responseType, scope, or redirectURI. The message names the field.
422 amb_capability_unavailable The customer's latest device doesn't advertise AUTH2 (needs iOS 16 / macOS 13+). Fall back to a different flow or a link.
The sheet opens but sign-in fails Your authorization server rejected the request — confirm the redirectURI is registered and the scope values are valid for your client.
No reply webhook arrives Confirm your webhook endpoint is reachable and subscribed to message events; see Receive webhooks locally.