> ## Documentation Index
> Fetch the complete documentation index at: https://docs.centralcart.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Identificação de cliente

> Como um sistema externo descobre, com segurança, qual cliente está logado na loja, sem pedir um segundo login.

Serve para quando uma página da loja precisa conversar com um sistema seu, e esse
sistema precisa saber com quem está falando. Uma área logada, um painel, um
formulário que já vem preenchido, um histórico que vive fora da CentralCart.

A divisão de responsabilidade é o ponto central:

|                  | CentralCart          | O seu sistema                       |
| ---------------- | -------------------- | ----------------------------------- |
| Responde         | **Quem** é o cliente | **O que fazer** com essa informação |
| Fonte da verdade | A sessão da loja     | A sua base                          |

A CentralCart atesta a identidade e para por aí. O que aquele cliente pode ver,
fazer ou receber é regra sua, e ela continua onde está hoje.

## Por que não dá para ler a sessão direto

Duas saídas parecem óbvias e as duas são armadilhas:

* **O cookie de sessão** é `httpOnly` e preso ao domínio da loja. O seu backend,
  em outro domínio, nunca o recebe.
* **Ler o e-mail pelo JavaScript da página** e enviar ao seu servidor não prova
  nada. Qualquer pessoa manda qualquer e-mail para o seu endpoint.

O passe existe para resolver exatamente isso: o navegador carrega um valor que
não serve para nada sozinho, e quem o troca por identidade é o seu servidor.

## Antes de começar

Crie uma chave de API na sua loja com o escopo **Identificar o cliente logado**
(`customers:identify`) e guarde como secret no seu backend.

<Warning>
  Essa chave nunca vai para o HTML nem para o JavaScript da página. Ela fica só
  no seu servidor. É ela que dá sentido ao passe, então quem tiver a chave pode
  resolver qualquer passe daquela loja.
</Warning>

## O fluxo

<Steps>
  <Step title="A página pede um passe">
    A chamada acontece no próprio domínio da loja, então a sessão do cliente é
    reconhecida sozinha. Não precisa enviar token nenhum.

    ```js theme={null}
    const res = await fetch('/auth/handoff')

    if (res.status === 401) {
      // ninguém logado nesta aba
      window.location.href = '/login'
      return
    }

    const { handoff, expires_in } = await res.json()
    ```
  </Step>

  <Step title="A página entrega o passe ao seu backend">
    ```js theme={null}
    const res = await fetch('https://your-system.com/api/session', {
      method: 'POST',
      credentials: 'include',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({ handoff }),
    })
    ```

    Libere CORS para o domínio da loja. Não é preciso cookie da CentralCart nessa
    chamada, porque a autenticação é o passe.
  </Step>

  <Step title="O seu backend troca o passe pela identidade">
    De servidor para servidor, com a sua chave de API.

    ```js theme={null}
    const r = await fetch('https://api.centralcart.io/v1/app/customer/identify', {
      method: 'POST',
      headers: {
        authorization: `Bearer ${CC_API_KEY}`,
        'content-type': 'application/json',
      },
      body: JSON.stringify({ handoff }),
    })

    if (!r.ok) return new Response('invalid handoff', { status: 401 })

    const { customer } = await r.json()
    ```

    O passe é consumido nesse instante. Uma segunda troca do mesmo passe falha.
  </Step>

  <Step title="O seu sistema segue com a regra dele">
    Agora você sabe quem é. O que vem depois é seu.

    ```js theme={null}
    const record = await yourDatabase.findByEmail(customer.email)

    if (!record) return new Response('not found', { status: 403 })

    // emita a sua própria sessão e devolva o que a página precisa
    ```

    Emita a sua sessão aqui. O passe serve para começar, não para repetir a cada
    requisição.
  </Step>
</Steps>

## A página completa

O exemplo abaixo é uma página do tema, com cabeçalho e rodapé normais da loja.

```html theme={null}
<% layout "layouts/layout", page_title: 'Minha área' %>

<div class="container py-12 min-h-screen">
  <% unless user %>
    <h1 class="text-2xl font-bold mb-2">Acesso restrito</h1>
    <p class="text-muted-foreground mb-6">Entre na sua conta para continuar.</p>
    <a href="/login" class="btn-primary">Fazer login</a>
  <% else %>
    <div id="app">Carregando...</div>

    <script>
      (async () => {
        const target = document.getElementById('app')

        const { handoff } = await fetch('/auth/handoff').then((r) => r.json())

        const res = await fetch('https://your-system.com/api/session', {
          method: 'POST',
          credentials: 'include',
          headers: { 'content-type': 'application/json' },
          body: JSON.stringify({ handoff }),
        })

        if (res.status === 403) {
          target.innerHTML = '<p>Você não tem acesso a esta área.</p>'
          return
        }

        const data = await res.json()
        // monte a página com o que o seu sistema devolveu
      })()
    </script>
  <% endunless %>
</div>
```

<Note>
  O `<% unless user %>` é experiência, não proteção. Ele evita mostrar uma tela
  quebrada para quem não está logado. Quem decide de verdade é o seu backend,
  porque é ele que guarda os dados.
</Note>

## Emitir o passe

```
GET /auth/handoff
```

Fica no domínio da loja, não em `api.centralcart.io`. É justamente por ser a
mesma origem que o cookie de sessão chega sozinho.

```json theme={null}
{
  "handoff": "V1StGXR8Z5jdHi6BmyT0eQx7KpLmN3cWaF9uYbZQsRtVhJdG",
  "expires_in": 60
}
```

Sem cliente logado, responde `401` com `{ "error": "CUSTOMER_UNAUTHENTICATED" }`.

O teto é de 20 passes por minuto, por cliente. Uma página legítima pede um por
carregamento.

## Trocar o passe

Ver [Identificar o cliente
logado](/api-reference/clientes/identificar-o-cliente-logado) para a referência
completa do endpoint.

```json theme={null}
{
  "customer": {
    "store_id": 1234,
    "email": "cliente@exemplo.com",
    "name": "Fulano de Tal",
    "avatar": null,
    "discord_id": null,
    "issued_at": "2026-09-22T14:03:11.284Z"
  }
}
```

O `email` é a identidade do cliente na loja, e é por ele que você casa com a sua
base.

## O que sustenta a segurança disso

* **O passe não serve sozinho.** Sem a chave de API da loja, ninguém o resolve.
* **Vale uma vez.** A primeira troca consome o passe.
* **Vale 60 segundos.** Tempo de atravessar da página para o seu servidor, e nada
  além disso.
* **Vale só na loja que o emitiu.** Uma chave de outra loja não resolve.
* **Não carrega sessão.** Trocar o passe devolve identidade, nunca um token que
  permita agir como aquele cliente.
* **Revogação imediata.** Revogue a chave no painel e a integração para na hora.

<Warning>
  Não coloque o passe na URL. Ele é credencial, e query string sobra em log de
  acesso, em `Referer` e no histórico do navegador. Por isso a troca é `POST` com
  o passe no corpo.
</Warning>

## Perguntas frequentes

<AccordionGroup>
  <Accordion title="O cliente precisa fazer um segundo login?">
    Não. Ele faz o login normal da loja, por código de e-mail, Google ou Discord,
    e a página usa essa sessão.
  </Accordion>

  <Accordion title="Dá para usar isso fora de uma página da loja?">
    Não. O passe depende do cookie de sessão, que só é enviado no domínio da
    loja. Para um front em outro domínio, use a [Storefront
    API](/api-reference/storefront/autenticacao-do-cliente), onde o próprio
    comprador faz login e recebe um token.
  </Accordion>

  <Accordion title="E se o que eu preciso saber já está na CentralCart?">
    Então não precisa de nada disso. Se a pergunta é "este cliente comprou tal
    produto", a tag `load orders` aceita `package_id` e responde direto no
    template, sem sistema externo no meio. Veja [Obtendo dados](/editor/tags).

    O passe existe para o caso contrário: a resposta está numa base que é sua.
  </Accordion>

  <Accordion title="Posso guardar o passe para usar depois?">
    Não. Ele morre em 60 segundos e no primeiro uso. Guarde a sua própria sessão
    depois da troca.
  </Accordion>
</AccordionGroup>
