Meta Business Agent Platform

User login

Updated: Sep 4, 2026
Copy for LLM
Verifying someone is a single moment. Being logged in is a state that persists, expires, and eventually has to end. A conversation that never establishes a session cannot personalize anything; one that never ends a session shows the second person to use a shared phone the first person’s order history. This page covers the session itself: starting anonymous, upgrading to a signed-in customer, deciding what that unlocks, renewing it before it lapses, and tearing it down at the right moment.
The agent handles:
  • Starting work as an anonymous guest so browsing needs no sign-in
  • Upgrading a guest to a signed-in customer, carrying their work across
  • Recognizing a returning customer without asking them to sign in again
  • Renewing a session that is about to expire, without interrupting the conversation
  • Gating each capability on the right level of identification, not all on one flag
  • Ending the session at completion, on request, or on inactivity

Who this guide is for

The examples use a train operator, but the pattern fits any business where some things need an identity and others do not. If everything needs sign-in, the session is trivial. If nothing does, you have no session. The interesting case is the mixture, which is most businesses.
Organization typeAvailable to a guestRequires a signed-in customer
Rail and coach operators
Timetables, fares, live departures
Existing bookings, changes, refunds, saved railcards
Gyms and fitness clubs
Class timetables, joining options, site facilities
Bookings, membership status, attendance history
Retail with accounts
Browsing, stock, prices, returns policy
Saved payment methods, loyalty balance, past orders
Events and ticketing
Event listings, venue information, seat maps
Your tickets, transfers, refunds
Membership organizations
Public events, joining information
Renewal status, member benefits, personal details
This guide is about the session. Establishing the identity that starts it is in Customer OTP authentication, and this page assumes you have read it: the connector configuration, the auth tool, and the token extraction described there are the foundation for everything below.
Each step below notes how the API is used in other scenarios, then applies it to the rail example.

How a session works

Meta Business Agent holds the customer credential for you. When a tool declares user_auth_action_config with user_action_tool_type set to auth, the token in that tool’s response is captured and stored against the conversation. Every later tool with user_auth_required set to true has that token injected at the location the connector’s user_auth_injection_config names.
Three consequences follow, and they shape the rest of this page.
The session is scoped to the conversation. It is not scoped to the phone number, and it does not survive into a different conversation.
The agent never handles the session credential. It is captured from a response and injected into a request without passing through anything the agent composes, which is why no agent instruction should ever mention the session token by name. Distinguish it from the short-lived verification artifact your passcode check returns: that one is an ordinary tool parameter, so the agent does carry it from the verification response into the auth tool that exchanges it for a session. Keep the artifact good for a single exchange — it is the only value in this flow that passes through the conversation.
Session state is not conversation memory. The credential persists because the platform stores it. Everything else — which account, how far through a task, what has been confirmed — is only as durable as your own systems make it.

Prerequisites

  • An onboarded and enabled Meta Business Agent (rollout.enabled: true) — see End-to-end setup
  • A valid system user access token (SUAT) or Business Integration System User (BISU) token with the whatsapp_business_messaging permission — see Generate an access token
  • Your WhatsApp Business phone number ID ({entity_id}) — find it in WhatsApp Manager⁠ under Account tools > Phone numbers
  • A connector configured with user_auth_injection_config, and an auth tool that returns a customer token — see Customer OTP authentication
  • An endpoint that issues a guest identifier, required by Start as a guest
  • An endpoint that merges guest work into a customer account on sign-in, required by Upgrade a guest to a customer
  • An endpoint that invalidates a session, required by End the session
  • A decision, written down, about which capabilities need which level of identification

Start as a guest

Do not open with a sign-in request. Most conversations never need one, and asking first is the fastest way to lose the ones that would have converted. Issue a guest identifier instead, so work done before sign-in has somewhere to live.
curl -X POST "https://api.facebook.com/{entity_id}/agent_connectors/{connector_id}/tools" \
  -H "Authorization: Bearer {ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -H "X-API-Version: 2.0.0" \
  -d '{
  "name": "start_guest_session",
  "description": "Creates an anonymous guest session and returns a guest identifier for holding work before the customer signs in. Call this once, the first time the conversation needs to record anything. Do not call it again in the same conversation, and do not call it after the customer has signed in.",
  "user_auth_required": false,
  "request_definition": {
    "method": "POST",
    "path": "/sessions/guest",
    "body": {
      "content_type": "application/json",
      "params": {
        "conversation_id": {
          "type": "string",
          "description": "The conversation this guest session belongs to.",
          "binding": {
            "kind": "macro",
            "macro": "WHATSAPP_CONVERSATION_ID"
          }
        }
      },
      "required": ["conversation_id"]
    }
  }
}'
“Call this once” is doing real work in that description. An agent that re-bootstraps a guest session mid-conversation abandons whatever the previous one held, and the symptom is a basket or a form that empties for no visible reason.
Bind the guest session to the conversation rather than to the phone number. A phone-scoped guest session is shared by everyone who uses that handset, which is exactly the leak the session is supposed to prevent.
Where else this applies. The guest-first pattern suits any flow with a meaningful anonymous phase: browsing, quoting, checking eligibility, filling in most of a form. Where every action needs an identity, skip this and verify at the start.

Recognize a returning customer

Some customers you already know. Bind the WhatsApp phone number and look them up before deciding whether a sign-in is needed at all.
curl -X POST "https://api.facebook.com/{entity_id}/agent_connectors/{connector_id}/tools" \
  -H "Authorization: Bearer {ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -H "X-API-Version: 2.0.0" \
  -d '{
  "name": "lookup_customer_by_phone",
  "description": "Checks whether this WhatsApp number is registered to a customer account and returns their first name and whether the account is active. Returns no personal data beyond the first name. Call this when the customer asks for something that needs an account, to decide whether to offer sign-in. A match here is not a sign-in and must never be treated as one.",
  "user_auth_required": false,
  "request_definition": {
    "method": "GET",
    "path": "/customers/lookup",
    "query_parameters": {
      "phone": {
        "type": "string",
        "description": "The WhatsApp phone number of the person messaging.",
        "required": true,
        "binding": {
          "kind": "macro",
          "macro": "WHATSAPP_PHONE_NUMBER"
        }
      }
    }
  }
}'
A match tells you the number is registered. It does not tell you who is holding the phone. Use it to make sign-in shorter — “Welcome back, Sam. Shall I send a code to finish signing you in?” — and never to skip sign-in entirely.
Return as little as possible. A first name personalizes the greeting; anything more is disclosed to whoever picked up the handset, before any verification has happened.
Where else this applies. Recognition-without-authentication is a useful middle state in most consumer flows, and a dangerous one if the boundary blurs. Keep the lookup deliberately thin so that even a full compromise of this call discloses nothing worth having.

Upgrade a guest to a customer

When the conversation reaches something that needs an identity, sign the customer in and carry their guest work across. The auth tool from Customer OTP authentication does the verification; the merge is what makes it feel continuous.
Pass the guest identifier into the sign-in so your service can attach the work to the account. verification_token here is the verification artifact, not the session credential: the agent supplies it from the passcode check, and the session token your endpoint returns is what the platform captures at user_auth_token_path and injects from then on.
curl -X POST "https://api.facebook.com/{entity_id}/agent_connectors/{connector_id}/tools" \
  -H "Authorization: Bearer {ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -H "X-API-Version: 2.0.0" \
  -d '{
  "name": "sign_in_customer",
  "description": "Completes sign-in for a verified customer and returns a session token. Pass the guest identifier from start_guest_session so anything collected before sign-in is attached to the account. Call this only after the passcode has been verified.",
  "user_auth_required": false,
  "user_auth_action_config": {
    "user_action_tool_type": "auth",
    "user_auth_token_path": "session.access_token",
    "refresh_token_path": "session.refresh_token",
    "expires_at_path": "session.expires_in",
    "expires_at_type": "relative_seconds"
  },
  "request_definition": {
    "method": "POST",
    "path": "/sessions/customer",
    "body": {
      "content_type": "application/json",
      "params": {
        "verification_token": {
          "type": "string",
          "description": "The short-lived token returned by passcode verification."
        },
        "guest_id": {
          "type": "string",
          "description": "The guest identifier from start_guest_session, when this conversation had one. Omit it if the customer signed in before doing anything as a guest."
        },
        "conversation_id": {
          "type": "string",
          "description": "The conversation this session belongs to.",
          "binding": {
            "kind": "macro",
            "macro": "WHATSAPP_CONVERSATION_ID"
          }
        }
      },
      "required": ["verification_token", "conversation_id"]
    }
  }
}'
Do the merge in your own service, not in the conversation. Given the choice, an agent will try to re-do the guest’s work under the new session — re-adding items, re-entering an address — and it will get some of it wrong. One call that transfers ownership server-side is both faster and correct.
Decide what happens when the guest work conflicts with what is already on the account. A saved address and a guest-entered address are both plausible; picking silently is how customers end up with deliveries to a house they moved out of. Prefer the value entered in this conversation, and say which one you used.
Where else this applies. Guest-to-customer merges appear anywhere anonymous work precedes identification. The rule that generalizes is that the merge is a server-side operation with a defined conflict policy, never a sequence of steps the agent replays.

Gate capabilities by level

A single signed-in flag is too blunt for most businesses. Set the level per capability, and set it in the tool rather than only in an agent instruction — the tool boundary is enforced, the instruction is guidance.
LevelHow it is establishedSuitable for
Anonymous
No identification at all
Product information, hours, policies, stock
Guest
start_guest_session
A basket, a quote, a partially completed form
Recognized
lookup_customer_by_phone matched
A personalized greeting, and nothing else
Signed in
Passcode verified, auth tool succeeded
Order history, saved details, most account actions
Re-verified
A second, fresh verification
Changing a payment method, address, or contact details
The last row is the one most often skipped. A conversation that has been open for twenty minutes has a signed-in session whose verification is twenty minutes old, and a phone that changes hands in that window carries the session with it. For anything whose consequences are hard to reverse, verify again at the point of action rather than relying on a session established earlier.
Express the gate as user_auth_required on the tool. A capability protected only by a sentence in an agent instruction will eventually be reached by a conversation that talked its way around the sentence.
Where else this applies. Any business that mixes low-risk and high-risk actions needs a ladder rather than a switch — a retailer separating browsing from saved cards, a utility separating tariff information from payment details, a ticketing platform separating listings from transfers. The platform itself gives you one boolean per tool, so the ladder lives in your design and your backend rather than in a field you can set. Write yours down before you configure anything: adding a rung later means revisiting every tool you have already shipped.

Keep the session alive

Conversations pause. Someone replies twenty minutes later, and a token issued for fifteen has expired. With a refresh tool configured, Meta Business Agent renews the session without involving the customer; without one, they are asked to verify again, which reads as the agent having forgotten them.
Configure the refresh tool as described in Customer OTP authentication. Two details matter for the session specifically.
Refresh tokens should rotate. Issue a new refresh token with each renewal and invalidate the previous one, so a captured token has a short useful life.
Refreshing is not re-verifying. A refreshed session is as old as its original verification, however many times it has been renewed. Keep the re-verification gate on high-risk actions tied to when the customer last proved who they were, not to when the token was last renewed.
Set the token lifetime deliberately. Long tokens keep conversations smooth and extend the window in which a shared phone is a problem. Short tokens do the opposite. For most consumer flows fifteen to thirty minutes of inactivity is a reasonable balance, with high-risk actions gated separately.

End the session

Sessions need to end, and the moments are predictable.
TriggerWhat should happen
The task completes
Invalidate the session once the order, change, or request is finished
The customer asks
Treat “log me out” as an instruction, and confirm it is done
Inactivity
Expire after a defined idle period rather than at the end of the day
Handoff to a person
Invalidate before transferring, so the colleague verifies independently
A different account is requested
Invalidate rather than switching accounts within a session
curl -X POST "https://api.facebook.com/{entity_id}/agent_connectors/{connector_id}/tools" \
  -H "Authorization: Bearer {ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -H "X-API-Version: 2.0.0" \
  -d '{
  "name": "end_session",
  "description": "Ends the signed-in session and invalidates its tokens. Call this when the customer asks to be logged out, when the task they signed in for is complete, and before handing the conversation to a colleague. Confirm to the customer that they are signed out.",
  "user_auth_required": true,
  "request_definition": {
    "method": "POST",
    "path": "/sessions/end"
  }
}'
The completion case is the one to get right. A booking conversation that ends with a ticket bought should not leave a session open on a phone that goes back in a pocket. End it, say so, and let the next request start again.
Never let one session serve two accounts. “Can you check my partner’s booking too?” is a reasonable-sounding request and a hard no: end the session, and start again as that person if they are present.
After ending a session, drop everything derived from it. A conversation that has signed out but still refers to the previous customer’s order has not really signed out, and that is what the customer sees.
Where else this applies. Explicit teardown matters more on a shared, persistent channel than in a browser, where closing a tab does much of the work. In a WhatsApp thread nothing closes, so if you do not end the session, nothing does.

Test the agent

Use Agent test to simulate conversations before going live. Pass conversation_id to continue a multi-turn exchange.
curl -X POST "https://api.facebook.com/{entity_id}/agent_test" \
  -H "Authorization: Bearer {ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -H "X-API-Version: 2.0.0" \
  -d '{
  "user_msg": "Can you show me my last booking?"
}'
Cover these scenarios in your test plan:
ScenarioWhat it verifies
Anonymous question, no sign-in offered
The agent does not gate what it need not gate
Guest work then sign-in
Guest content survives the merge
Guest address conflicts with saved address
The conflict policy runs and is stated
Returning number recognized
Greeting personalizes without disclosing
Recognized but never verified
No account data reaches the conversation
Token expires mid-conversation
Refresh runs without re-verification
High-risk action on an old session
Re-verification is demanded
Explicit sign-out
Session ends and is confirmed
Question after sign-out
Nothing from the previous session survives
Second account requested
Refused, session ended
Handoff while signed in
Session invalidated before transfer
Two sign-ins in one conversation
The second does not inherit the first
The last six deserve automated regressions. Every one of them is a case where the agent behaves reasonably and the session boundary is what fails.
Where else this applies. Session bugs hide in transitions rather than in steady states. Test the moments where state changes hands — merge, refresh, sign-out, handoff — far harder than the signed-in path itself.

Example conversation

This exchange shows the configuration above in use: anonymous browsing, a guest session, an upgrade that carries the held seat, a re-verification for a high-risk change, and a clean teardown.
Turns annotated with (calls ...) use the four tools defined on this page. Holding a seat, booking the ticket, and changing the saved card are not configured here — those belong to your reservation and payment systems, and are shown only so the session transitions read as a whole conversation.
Customer: What time is the last train to Leeds on Friday evening?
Agent: The last direct service leaves at 21:38 and arrives at 23:52. There are earlier ones at 18:05 and 19:20 if you would rather not travel that late.
Customer: Hold me a seat on the 18:05
Agent:(calls start_guest_session)
Held — one seat on the 18:05 to Leeds, coach C, seat 41. That is £42.50 as a standard single.
Customer: Do I have a railcard saved? It should be cheaper than that.
Agent:(calls lookup_customer_by_phone)
Welcome back, Sam. Railcard discounts need you signed in before I can check for one — shall I send a code to the mobile on your account?
Customer: Yes
Agent:(verification runs, then calls sign_in_customer)
Signed in, and your seat on the 18:05 is still held. Your Two Together Railcard is valid until March, which brings the single down to £28.35.
Customer: Book it on my saved card — but use the new one ending 4471
Agent: I can book it now. Changing the card on your account needs one more check, so I’ll send another code first.
(re-verification runs)
Card updated, and your ticket is booked — coach C, seat 41 on the 18:05 to Leeds this Friday.
Customer: Thanks, that’s everything
Agent:(calls end_session)
All done — I’ve signed you out. Message any time if you need anything else.

Monitor connector health

The connector logs endpoint, GET /{entity_id}/agent_connectors/{connector_id}/logs, reports how your session endpoints are answering.
curl -X GET "https://api.facebook.com/{entity_id}/agent_connectors/{connector_id}/logs?summary_only=true&top_n=5&include_stats=true" \
  -H "Authorization: Bearer {ACCESS_TOKEN}" \
  -H "X-API-Version: 2.0.0"
Logs cover the last seven days, and the requested time range cannot exceed seven days. The response carries two objects:
ObjectFieldWhat it reports
data
failure_code_name
The failure category
data
error_message
The message returned by your endpoint
data
tool_name
Which tool made the call
data
occurrences
How many times this failure appeared
stats
success_rate
Share of calls that succeeded
stats
avg_latency_s
Mean round-trip time in seconds
stats
p95_latency_s
95th percentile round-trip time in seconds
stats
p99_latency_s
99th percentile round-trip time in seconds
Three signals are specific to sessions and none of them are visible in the overall success rate.
Authentication failures on tools with user_auth_required set to true. A cluster of these usually means an expiry misconfiguration rather than genuinely expired sessions.
start_guest_session called more than once per conversation. Each extra call is work silently discarded.
Sign-ins without a matching end_session. A persistent gap means sessions are being left open, which is the failure with the longest tail.
Where else this applies. Session health is a ratio problem rather than an error-rate problem. The counts that matter are between tools — guests per conversation, sign-ins against sign-outs, refreshes against verifications — and a connector dashboard showing only success rates will look healthy throughout.

Tips for this use case

These are the session decisions that most often go wrong once the agent is live.
Do not ask for sign-in up front. Most conversations never need it, and asking first costs you the ones that would have gone somewhere.
Scope guest sessions to the conversation. A phone-scoped guest session is shared by everyone who uses the handset.
Bootstrap the guest session exactly once. A second call quietly discards whatever the first one held.
Recognition is not authentication. A matched phone number shortens sign-in. It never replaces it.
Return only a first name from the lookup. Anything more is disclosed before verification to whoever is holding the phone.
Merge server-side with a stated conflict policy. Replaying guest work through the agent produces near-misses, and silent conflict resolution produces deliveries to old addresses.
Use levels, not a boolean. Anonymous, guest, recognized, signed in, and re-verified are five different things, and collapsing them either blocks harmless requests or permits harmful ones.
Re-verify for irreversible actions. A session established twenty minutes ago is not evidence about who is holding the phone now.
Rotate refresh tokens, and remember refreshing is not re-verifying. A renewed session is as old as its original verification.
End sessions on completion, not just on request. Most customers never ask to sign out, and a session left open on a shared phone is the failure with the longest tail.
Never switch accounts inside a session. End it and start again.

Summary checklist

Confirm each of the following before you enable the agent on a production phone number.
  • Wrote down which capabilities need anonymous, guest, recognized, signed-in, or re-verified status
  • Confirmed anonymous questions are answered without any sign-in prompt
  • Defined start_guest_session scoped to WHATSAPP_CONVERSATION_ID, not to the phone number
  • Instructed the agent to bootstrap a guest session exactly once per conversation
  • Limited lookup_customer_by_phone to a first name and an active flag
  • Confirmed a phone-number match never unlocks account data on its own
  • Passed guest_id into sign-in and performed the merge server-side
  • Defined and documented the conflict policy for guest data against saved data
  • Set user_auth_required to true on every tool that returns account data
  • Added a re-verification gate on payment, address, and contact detail changes
  • Configured a refresh tool and confirmed refresh tokens rotate
  • Confirmed the re-verification gate keys off last verification, not last refresh
  • Set a token lifetime appropriate to the sensitivity of the data
  • Defined end_session and called it on completion, on request, and before handoff
  • Confirmed nothing from a previous session survives sign-out
  • Confirmed an account switch inside a session is refused
  • Tested merge, expiry, refresh, sign-out, handoff, and repeated sign-in
  • Monitored guests per conversation and sign-ins against sign-outs, not only success rates