MyCustomerPilotMyCustomerPilot
Customer identity & auth

Knowing which customer is asking

Whether a tool needs to know who's asking — and if so, how strongly to verify them — is a short decision you set up once, per tool. This is the ladder from anonymous to fully signed-in.

Two separate auth layers — don't conflate them
MyCustomerPilot ↔ your APIis solved by the tool’s credential + our HMAC signature. Your end customer’s identity — which lives in your login system — is what this page is about.

The decision, in three questions

For any capability you expose as a tool, walk these once at design time:

  1. 1
    Is identity even needed?Public info (prices, stock) or a reference-gated lookup (“track by tracking number”) needs none. Personal → continue.
  2. 2
    Is the action sensitive?Low-risk personal (“where’s my order?”) → the phone is enough. Reveals personal/financial data, or changes something → step-up required.
  3. 3
    Which step-up — a code or a login? Decided by your architecture, not by how secret the data is (next section). And for high-risk, irreversible actions, re-verify every time.

Tiers 1–2: from public to phone-as-identity

Every tool call carries the customer’s WhatsApp number — Meta verified they possess it, we received it on a signature-verified webhook, and we forward it HMAC-signed. It arrives in context.contact_phone and the X-MCP-Contact-Phone header, as digits with no +.

tool-call
HTTP
POST https://your-api.com/orders/status
X-MCP-Signature: t=1699999999,v1=8f3a…       # verify this first
X-MCP-Contact-Phone: 2348012345678            # the customer's WhatsApp number

{
  "arguments": { "order_ref": "A-1024" },
  "context": { "contact_phone": "2348012345678", "conversation_id": "cv_01H…" }
}

It only identifies someone if the number is on file

The phone matches a customer only when you key customers by phone and they message from the same numberthat’s on their account. So: collect a phone at signup, tell customers to message from their registered number, and treat a no-match as “not identified yet” — the one-time code below is how you connect a mismatched or missing number.

A matched phone is possession, not proof
Numbers get recycled and SIMs get swapped. A phone match is fine for low-risk reads, but not proof of the person for anything sensitive — which is exactly why sensitive actions step up.

Is this action sensitive?

You classify each tool — it’s domain-specific, and only you can judge it safely. A simple rule to adopt and refine:

  • Not sensitive — public info, and lookups gated by a reference the customer holds (order/tracking number). Phone-match, or nothing, is fine.
  • Sensitive → step up— anything that reveals personal or financial data tied to identity (balance, saved addresses, “my order history”), or that changes state (update details, cancel, pay, redeem).

Enforce it where the data lives — in your tool endpoint. For a sensitive action from a contact you haven’t verified, return a soft verification_required result rather than the data; the agent takes it from there.

Verifying identity: the one-time code (start here)

Integration preview
The step-up recipe runs on primitives we already ship — the signed envelope + ordinary tool calls. There’s no first-class “verify” button yet; you wire the OTP on your side, which most businesses already have.

This is the defaultway to authorize a sensitive action — and the way to connect a WhatsApp number you couldn’t match. It works whenever your backend can serve the customer’s data from a trusted server-to-server call once it knows who they are — which is most businesses. You only confirm identity once; no customer token is involved.

  1. 1
    Your tool returns verification_required. The agent offers to verify: “I can send a code to the email on your account — shall I?”
  2. 2
    You send a one-time code to the customer’s on-file email or phone (your channel, your template).
  3. 3
    The customer types the code in chat; the agent passes it to your verify tool.
  4. 4
    You confirm the code, link this WhatsApp number to the customer, and the original action retries — now served from your own records.
step-up
HTTP
# A sensitive action from a contact you haven't verified yet.
# Your endpoint responds with a soft "verify first" — not an error:
{ "status": "verification_required",
  "message": "We'll send a code to the email on your account." }

# You send an OTP to the customer's ON-FILE email/phone; the agent
# collects the code in chat and calls your verify tool:
POST https://your-api.com/account/verify
{ "code": "418290", "contact_phone": "2348012345678" }
# → you link this WhatsApp number to the customer, and the action retries.
Passwords never touch chat
Step-up is a code you send out-of-band, never a password typed into WhatsApp. Transcripts persist on the customer’s phone and in our systems — collecting a password there is a compliance liability, and we decline flows that require it.

Connect your existing login (Preview)

Integration preview
Escalate to this onlywhen the one-time code can’t work — because your protected endpoints require the customer’s own login tokenand there is no trusted server-to-server path. It’s an integration pattern you assemble today; first-class support is on the roadmap.

If your API is genuinely login-guarded per user, the fix isn’t a code — it’s the customer’s actual session. The good news: your app’s own API is already the tool surface, so there’s little new on the data side. The one new piece is a token handoff:

  1. 1
    The agent offers a Sign in button that opens your own login page, carrying a signed, one-time, expiring link_tag.
  2. 2
    The customer signs in as they always do — passwords stay on your page.
  3. 3
    On success, your backend POSTs the token (and a refresh token) to our callback.
  4. 4
    We store it encrypted per (account, customer)and attach it on that customer’s tool calls; we refresh it to keep them served, and only re-send the button on a hard expiry.
link-callback
HTTP
// Tier 4 only. After the customer signs in on YOUR login page,
// your backend POSTs their token to our callback (server-to-server).
POST https://api.mycustomerpilot.com/v1/identity/link
Authorization: Bearer mcp_sk_live_…
{
  "link_tag": "lt_01H…",        // the signed, one-time tag from the button
  "access_token": "cust_…",     // the token your protected endpoints expect
  "refresh_token": "cref_…",    // optional — lets us renew without re-linking
  "expires_in": 3600
}
Draft — pending review
The callback path (/v1/identity/link) and token-lifetime details here are a working draft, not a finalized contract. Treat the shape as illustrative until this ships.

High-risk actions: always re-verify

For irreversible or money-moving actions — a transfer, changing the login email or password — re-verify every time with a fresh code, even if the customer is already linked or logged in. A persistent link or a live session is only as strong as its weakest moment; a fresh check per high-risk action is the norm (banks do exactly this). Flag which of your actions count as high-risk.

Which do I use?

  • Not sensitive → nothing, or phone-match.
  • Sensitive, and your backend can serve by customer once identified → one-time code (the default).
  • Sensitive, and your endpoints require the customer’s own token → login relay (Preview).
  • High-risk / irreversible → fresh code every time, on top of the above.

You decide this once per tool, by how your API is built — not per conversation. The agent just runs whatever each tool asks for. Authenticating your integration (the API key) is in Authentication.