Identification without password
The agent discovers who the customer is by linking the channel with a one-time code — no password passes through the conversation — and only then queries plan and invoices.
Updated on Sep 02, 2026
The question that comes up in every deployment: "And when the customer needs to identify themselves? Do I ask for username and password?"
No. A password typed in the chat becomes a message — and that message goes into the conversation history, into the model's context, and into the stream of whoever is watching the run. Once it's there, it doesn't leave.
What you do instead is what this flow shows: link the channel to the account just once, with a one-time code sent to the registered email. After that, the system itself knows whose WhatsApp this is — and the agent never needs to ask who the person is, nor handle an account id.
The property that makes this secure
Personal data routes (my_plan, my_invoices) don't receive account id. They receive channel + identifier — the channel address, which is not a secret — and it's your back-end that resolves whose account it is based on the link. That way, even if the model makes a mistake or someone tries to prompt-inject it, there's no other person's id for it to pass along.
How to import
On the platform: Agents → Import, choose the downloaded file. It enters as a new draft (nothing existing is changed), with renewed observability ids. Then fill in what belongs to your environment — credentials, URLs and knowledge bases — and publish.
{
"format": "hinow.agent",
"version": 1,
"exported_at": "2026-09-01T00:00:00Z",
"credentials_included": false,
"agent": {
"name": "Identificação sem senha",
"description": "O agente descobre quem é o cliente por vínculo de canal com código de uso único — nenhuma senha passa pela conversa — e só então consulta plano e faturas.",
"avatar": null,
"model": null,
"system_prompt": null,
"config": {},
"tools": null,
"workflow": {
"nodes": [
{
"id": "start",
"type": "start",
"position": {
"x": 300,
"y": 0
},
"data": {
"label": "Início",
"variables": [
{
"name": "channel",
"type": "input",
"required": true,
"description": "Canal em que a conversa chegou: whatsapp, webchat ou email."
},
{
"name": "identifier",
"type": "input",
"required": true,
"description": "Identificador da pessoa neste canal (telefone, id de sessão). Não é segredo: é só o endereço do canal."
}
]
}
},
{
"id": "guard-1",
"type": "guardrails",
"position": {
"x": 288,
"y": 150
},
"data": {
"name": "Porteiro",
"config": {
"target": "input",
"checks": {
"pii": true
},
"fail_message": "A mensagem parece conter um dado sensível (documento, cartão ou telefone completo)."
}
}
},
{
"id": "agent-1",
"type": "agent",
"position": {
"x": 295,
"y": 330
},
"data": {
"name": "conta",
"model": "hinow/himax",
"system_prompt": "Você é o assistente da conta do cliente na Vega Telecom, atendendo pelo canal em que a pessoa escreveu.\n\nDADOS DESTE CANAL — use exatamente estes valores nas ferramentas, sem perguntar e sem inventar:\n- channel: {{channel}}\n- identifier: {{identifier}}\n\nIDENTIDADE — LEIA ANTES DE QUALQUER COISA\nVocê NÃO sabe quem é a pessoa até o sistema dizer. E você NUNCA pede senha, token, número de cartão ou documento — nem para \"confirmar identidade\". Se a pessoa mandar algo assim por conta própria, peça para não repetir e siga sem usar o valor.\n\nO caminho correto é sempre este:\n1. verificar_canal — descobre se este canal já está ligado a uma conta. Faça isso ANTES de qualquer consulta de dado pessoal.\n2. Se já estiver ligado: siga normalmente, chamando a pessoa pelo nome.\n3. Se não estiver: peça só o E-MAIL da conta e chame solicitar_codigo. Explique que vai chegar um código de 6 dígitos no e-mail.\n4. Quando a pessoa disser o código, chame confirmar_codigo. Se der certo, o canal fica ligado para sempre — nunca mais peça código.\n5. Se o código falhar, diga o motivo (errado, expirado) e ofereça pedir outro. Depois de 3 tentativas sem sucesso, ofereça atendimento humano.\n\nRepare: você nunca informa o id da conta para as ferramentas de dado pessoal. É o sistema que resolve a conta a partir do canal ligado — e é por isso que este desenho é seguro.\n\nO QUE VOCÊ RESPONDE\n- Fatura, consumo, plano e status do serviço: sempre pelas ferramentas, com o valor exato.\n- Como funciona o serviço, prazos, regras e procedimentos: pela base de conhecimento.\n- Quando não souber: diga que não sabe e ofereça abrir atendimento humano.\n\nTOM\nCurto, claro, sem jargão. Uma pergunta de cada vez.",
"config": {
"temperature": 0.3,
"max_tool_loops": 12
}
}
},
{
"id": "agent-pii",
"type": "agent",
"position": {
"x": 55,
"y": 330
},
"data": {
"name": "aviso",
"model": "hinow/himax",
"system_prompt": "A mensagem do cliente traz um dado sensível (documento, cartão ou telefone completo). Peça, com gentileza e em uma frase, que ele não envie esse tipo de dado por aqui, explique que não é necessário para o atendimento, e convide a continuar com a dúvida em palavras. Não repita o valor recebido.",
"config": {
"temperature": 0.4,
"tools_filter": []
}
}
},
{
"id": "rag-1",
"type": "rag_search",
"position": {
"x": 575,
"y": 260
},
"data": {
"label": "Base da operadora",
"config": {
"rag_ids": [],
"top_k": 4,
"min_score": 0.35
}
}
},
{
"id": "hook-1",
"type": "webhook",
"position": {
"x": 575,
"y": 405
},
"data": {
"label": "API de contas",
"config": {
"name": "Contas Vega",
"baseUrl": "https://api.suaempresa.com/accounts/v1",
"timeout": 15000,
"auth": {
"type": "bearer",
"token": "COLE_SEU_TOKEN_AQUI"
},
"retryOnError": true,
"maxRetries": 2,
"routes": [
{
"id": "r-verificar_canal",
"name": "verificar_canal",
"method": "GET",
"path": "/identity/channel",
"description": "Diz se este canal (WhatsApp, e-mail, chat) já está ligado a uma conta e, se estiver, o primeiro nome do titular.",
"whenToUse": "SEMPRE no início da conversa, antes de qualquer consulta de dado pessoal.",
"responseDescription": "Se está ligado, o primeiro nome do titular; se não, a indicação de que precisa vincular.",
"parameters": [
{
"name": "channel",
"type": "string",
"required": true,
"description": "Canal desta conversa: whatsapp, webchat ou email",
"howToObtain": "É o canal em que a mensagem chegou",
"example": "whatsapp"
},
{
"name": "identifier",
"type": "string",
"required": true,
"description": "Identificador da pessoa neste canal",
"howToObtain": "No WhatsApp é o telefone de quem escreve; no webchat é o id da sessão",
"example": "5511987654321"
}
],
"enabled": true,
"queryParams": {
"channel": "{{channel}}",
"identifier": "{{identifier}}"
}
},
{
"id": "r-solicitar_codigo",
"name": "solicitar_codigo",
"method": "POST",
"path": "/identity/link/request",
"description": "Envia um código de 6 dígitos para o e-mail cadastrado na conta, para ligar este canal a ela.",
"whenToUse": "Quando verificar_canal disser que o canal não está ligado e a pessoa informar o e-mail da conta. Nunca peça senha no lugar disso.",
"responseDescription": "Confirmação de envio (sem revelar o e-mail completo) ou aviso de que o e-mail não corresponde a nenhuma conta.",
"parameters": [
{
"name": "email",
"type": "string",
"required": true,
"description": "E-mail cadastrado na conta",
"howToObtain": "Pergunte à pessoa qual o e-mail da conta",
"example": "cliente@exemplo.com"
},
{
"name": "channel",
"type": "string",
"required": true,
"description": "Canal desta conversa",
"howToObtain": "O mesmo de verificar_canal",
"example": "whatsapp"
},
{
"name": "identifier",
"type": "string",
"required": true,
"description": "Identificador neste canal",
"howToObtain": "O mesmo de verificar_canal",
"example": "5511987654321"
}
],
"enabled": true,
"bodyTemplate": "{\"email\": \"{{email}}\", \"channel\": \"{{channel}}\", \"identifier\": \"{{identifier}}\"}"
},
{
"id": "r-confirmar_codigo",
"name": "confirmar_codigo",
"method": "POST",
"path": "/identity/link/confirm",
"description": "Confere o código recebido por e-mail e liga este canal à conta em definitivo.",
"whenToUse": "Logo depois que a pessoa disser o código. Se der certo, nunca mais peça código neste canal.",
"responseDescription": "Se o canal foi ligado e o nome do titular, ou o motivo da recusa (código errado, expirado, sem pedido).",
"parameters": [
{
"name": "code",
"type": "string",
"required": true,
"description": "Código de 6 dígitos",
"howToObtain": "A pessoa dita na conversa",
"example": "482913"
},
{
"name": "channel",
"type": "string",
"required": true,
"description": "Canal desta conversa",
"howToObtain": "O mesmo de solicitar_codigo",
"example": "whatsapp"
},
{
"name": "identifier",
"type": "string",
"required": true,
"description": "Identificador neste canal",
"howToObtain": "O mesmo de solicitar_codigo",
"example": "5511987654321"
}
],
"enabled": true,
"bodyTemplate": "{\"code\": \"{{code}}\", \"channel\": \"{{channel}}\", \"identifier\": \"{{identifier}}\"}"
},
{
"id": "r-meu_plano",
"name": "meu_plano",
"method": "GET",
"path": "/account/plan",
"description": "Plano contratado, velocidade, franquia e valor mensal do titular deste canal.",
"whenToUse": "Depois que o canal estiver ligado, quando a pergunta for sobre plano, pacote ou mensalidade.",
"responseDescription": "Nome do plano, valor, benefícios e data de renovação.",
"parameters": [
{
"name": "channel",
"type": "string",
"required": true,
"description": "Canal desta conversa",
"howToObtain": "O mesmo de verificar_canal",
"example": "whatsapp"
},
{
"name": "identifier",
"type": "string",
"required": true,
"description": "Identificador neste canal",
"howToObtain": "O mesmo de verificar_canal",
"example": "5511987654321"
}
],
"enabled": true,
"queryParams": {
"channel": "{{channel}}",
"identifier": "{{identifier}}"
}
},
{
"id": "r-minhas_faturas",
"name": "minhas_faturas",
"method": "GET",
"path": "/account/invoices",
"description": "Últimas faturas do titular deste canal, com status de pagamento.",
"whenToUse": "Depois que o canal estiver ligado, quando a pergunta for sobre fatura, boleto, vencimento ou pagamento.",
"responseDescription": "Lista com competência, valor, vencimento, status e link da segunda via.",
"parameters": [
{
"name": "channel",
"type": "string",
"required": true,
"description": "Canal desta conversa",
"howToObtain": "O mesmo de verificar_canal",
"example": "whatsapp"
},
{
"name": "identifier",
"type": "string",
"required": true,
"description": "Identificador neste canal",
"howToObtain": "O mesmo de verificar_canal",
"example": "5511987654321"
},
{
"name": "limite",
"type": "number",
"required": true,
"description": "Quantas faturas trazer. Use 3 quando a pessoa não especificar — nunca deixe em branco",
"example": "3"
}
],
"enabled": true,
"queryParams": {
"channel": "{{channel}}",
"identifier": "{{identifier}}",
"limite": "{{limite}}"
}
}
]
}
}
},
{
"id": "end-1",
"type": "end",
"position": {
"x": 305,
"y": 510
},
"data": {
"label": "Fim",
"status": "success"
}
}
],
"edges": [
{
"id": "e-start-guard-1-d",
"source": "start",
"target": "guard-1"
},
{
"id": "e-guard-1-agent-1-pass",
"source": "guard-1",
"target": "agent-1",
"sourceHandle": "pass"
},
{
"id": "e-guard-1-agent-pii-fail",
"source": "guard-1",
"target": "agent-pii",
"sourceHandle": "fail"
},
{
"id": "e-rag-1-agent-1-slot-1",
"source": "rag-1",
"target": "agent-1",
"targetHandle": "slot-1"
},
{
"id": "e-hook-1-agent-1-tool",
"source": "hook-1",
"target": "agent-1",
"sourceHandle": "tool",
"targetHandle": "slot-2"
},
{
"id": "e-agent-1-end-1-d",
"source": "agent-1",
"target": "end-1"
},
{
"id": "e-agent-pii-end-1-d",
"source": "agent-pii",
"target": "end-1"
}
]
}
}
}| Card | Why it's here | What it does |
|---|---|---|
| **Start** | Brings the channel address. | channel and identifier arrive in the run's variables. They're not a secret — they're the phone/session of whoever wrote. |
| **Gatekeeper (guardrails)** | Safety net. | PII check (deterministic, no cost): CPF, CNPJ, card, email and phone. fail output diverts before the agent sees it. |
**Agent warning** | The sensitive data path. | Politely asks not to send that, without repeating the value. tools_filter: []: no tools at all. |
**Agent account** | The actual service. | Follows the identity flow: verify channel → (if needed) code → query. Never asks for password. |
| **Webhook** (5 routes) | Identity and account data. | 3 linking routes + 2 query routes. All by channel/identifier. |
| **Knowledge (RAG)** | What's not personal. | How the service works, deadlines, procedures. Doesn't depend on knowing who the person is. |
- 1
`verify_channel`
Always first. Says whether this WhatsApp is already linked to an account and the account holder's first name.
- 2
Already linked? Move on
The agent calls by name and responds. No identity questions.
- 3
Not linked: asks only for email
request_codesends a 6-digit code to the registered email — whoever doesn't have access to the email doesn't move forward. - 4
`confirm_code`
The person dictates the code, the channel stays linked permanently. The code is never requested again on that channel.
- 5
Query released
my_planandmy_invoicesrespond for the channel owner — no account id in transit.
Three things your back-end needs to do
1. Expire the code (5–10 minutes) and limit attempts. 2. Never reveal the full email in the request_code response (c***@example.com) — otherwise the route becomes an email discovery tool. 3. Respond the same way for emails that exist and that don't exist, for the same reason.
Then the exchange happens outside the conversation, not inside it:
- Your application already authenticated the person (app, logged-in area, portal): they send the identifier in the
variablesof the run and you don't even need this flow — it's the standard for Support with tickets. - The conversation starts in an open channel (WhatsApp, Instagram): use this flow. The one-time code replaces the password with better security, not worse.
- You really need per-user credentials in the HTTP call: there's a path where the secret doesn't pass through the model — the MCP card resolves
{{variables}}in the connection headers. It's explained in Credentials and identity.
What never works well is asking for the password in the chat and sending it to a login route: the value stays in the history, the token back too, and you just turned a conversation into a vault.
- Another channel:
channelaccepts whatever you want (whatsapp,webchat,instagram,email) — the link is by channel+identifier pair. - Another second factor: SMS instead of email, if the registration has a verified phone. The design doesn't change.
- More queries: each piece of owner data becomes a new route per
channel/identifier. Never by account id coming from the model. - Stricter guardrails:
blocklistfor forbidden terms andjailbreak/moderation(these cost one classification per message).

