Pangeia ID — Guia de Integração (v2.1)
Guia oficial para integrar login Pangeia ID em qualquer site ou app. Uma conta única para o usuário; autenticação server-to-server segura para você.
SDK: pangeia-id.js v2.2.0 — suporta modo popup e modo modal (iframe inline). Compatível com v2, sem breaking changes.
Antes de começar: obtenha sua API Key
Toda chamada server-to-server ao Pangeia ID requer uma API Key. Para obtê-la:
- Acesse seu dashboard Pangeia ID e vá para a aba API Keys.
- Ative o Modo Desenvolvedor no canto superior direito da seção.
- Clique em Gerar nova API Key e informe o nome do seu app.
- Copie o valor do campo API Key e guarde no backend do seu app. Nunca exponha no frontend.
PANGEIA_API_KEY=pk_live_...
TL;DR (em 30 segundos)
- Adicione o script
pangeia-id.jsna sua página. - Crie um botão com classe
.pangeiaid-loginedata-exchange-url. - Implemente no seu backend o endpoint
POST /auth/exchange-hash. - Seu backend chama
POST /api/auth/exchange-hashno Pangeia com sua API Key. - Com
pid + userna resposta, crie a sessão local e pronto.
Passo a Passo (modo newbie)
Passo 1 — Frontend: botão pronto
<button class="pangeiaid-login" data-exchange-url="/auth/exchange-hash">
Entrar com Pangeia ID
</button>
<script defer src="https://id.pangeialabs.com/static/pangeia-id.js"></script>
<script defer src="/static/js/pangeia-auth.js"></script>
O SDK vincula automaticamente o clique ao botão. Exemplo de /static/js/pangeia-auth.js:
document.addEventListener("pangeia:authenticated", function (ev) {
// ev.detail.result contém { ok, pid, user }
console.log("Usuário autenticado:", ev.detail.result.user.name);
window.location.reload();
});
document.addEventListener("pangeia:cancel", function (ev) {
// ev.detail.info.reason: "user_closed" | "escape" | "backdrop_click" | "popup_closed" | "popup_blocked" | "timeout"
console.log("Login cancelado:", ev.detail.info.reason);
});
document.addEventListener("pangeia:error", function (ev) {
console.error("Erro Pangeia:", ev.detail.error);
});
CSP recomendada (sem unsafe-inline)
Não use onclick/onload no HTML nem <script>...</script> inline — use arquivos externos com defer.
Content-Security-Policy:
default-src 'self';
script-src 'self' https://id.pangeialabs.com;
connect-src 'self' https://id.pangeialabs.com;
frame-ancestors 'self';
base-uri 'self';
Passo 2 — Backend: endpoint local de exchange
Esse endpoint recebe o login_hash do SDK e o troca no Pangeia pelo objeto de usuário.
Exemplo Python (Flask)
import os
import requests
from flask import Flask, request, jsonify, session
app = Flask(__name__)
app.secret_key = "troque-isto"
PANGEIA_BASE = "https://id.pangeialabs.com"
PANGEIA_API_KEY = os.environ["PANGEIA_API_KEY"]
@app.post("/auth/exchange-hash")
def auth_exchange_hash():
data = request.get_json() or {}
login_hash = (data.get("login_hash") or "").strip()
if not login_hash:
return jsonify({"ok": False, "error": "login_hash obrigatória"}), 400
resp = requests.post(
f"{PANGEIA_BASE}/api/auth/exchange-hash",
headers={"Authorization": f"Bearer {PANGEIA_API_KEY}", "Content-Type": "application/json"},
json={"login_hash": login_hash},
timeout=15,
)
out = resp.json()
if not resp.ok or not out.get("ok"):
return jsonify(out), resp.status_code
session["pid"] = out["pid"]
session["user_email"] = out["user"]["email"]
return jsonify(out), 200
Exemplo Node.js (Express)
import express from "express";
const app = express();
app.use(express.json());
const PANGEIA_BASE = "https://id.pangeialabs.com";
const PANGEIA_API_KEY = process.env.PANGEIA_API_KEY;
app.post("/auth/exchange-hash", async (req, res) => {
const login_hash = (req.body?.login_hash || "").trim();
if (!login_hash)
return res.status(400).json({ ok: false, error: "login_hash obrigatória" });
const r = await fetch(`${PANGEIA_BASE}/api/auth/exchange-hash`, {
method: "POST",
headers: { "Authorization": `Bearer ${PANGEIA_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ login_hash }),
});
const out = await r.json();
return res.status(r.status).json(out);
});
Passo 3 — Consultar dados pelo PID (opcional)
Use quando precisar dos dados do usuário em chamadas posteriores, sem exigir novo login.
curl -X POST https://id.pangeialabs.com/api/user/by-pid \
-H "Authorization: Bearer SUA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"pid":"pid_xxx"}'
Contrato HTTP — endpoints do servidor Pangeia
Todos os endpoints abaixo são chamados pelo seu backend, nunca pelo browser. Autenticação via Authorization: Bearer <api_key>.
Formato de erro padronizado
Toda resposta de erro de validação/negócio segue o shape:
{
"ok": false,
"code": "NOME_DO_ERRO",
"message": "Descrição legível do problema."
}
Exceção: falhas de infraestrutura (ex.: banco de dados temporariamente indisponível durante um failover do cluster) retornam 503 com um shape diferente — {"error": "temporarily_unavailable", "message": "..."}, sem ok nem code. Sempre trate qualquer resposta não-2xx como erro, mesmo quando o shape não bater com o padrão acima.
POST /api/auth/exchange-hash
Body: { "login_hash": "temp_..." }
Resposta de sucesso:
{
"ok": true,
"pid": "pid_...",
"user": {
"id": "...",
"name": "...",
"display_name": "...",
"username": "nome-de-usuario",
"aliases": ["nome-de-usuario-alt"],
"email": "principal@email.com",
"emails": [{ "email": "outro@email.com", "verified": true }],
"phone": "...",
"taxid": "...",
"birth_date": "...",
"avatar_url": "https://...",
"verified_id": false,
"created_at": "2025-01-10T00:00:00+00:00",
"updated_at": "2026-06-18T18:07:00+00:00"
}
}
Campos do objeto user:
display_name— nome de exibição pública, definido pelo próprio usuário. Não é único — outros usuários podem ter o mesmo valor. Se o usuário não definir, vem igual aname. Use este campo (e nãoname) sempre que for exibir o usuário publicamente.username— slug único (ex:"joao-silva"). String vazia se ainda não definido. Formato: 3–30 caracteres, apenas letras minúsculas, números e hífen — não pode começar/terminar com hífen nem ter hífen duplo.aliases— lista de aliases reservados pelo usuário para ousername, com o mesmo formato dele. Array vazio se não tiver nenhum. Vem sempre completa (não incremental) — substitua a lista inteira que você tem salva pela recebida em cada payload, nunca faça merge item a item.email— e-mail principal verificado. Use como identificador de e-mail.emails— e-mails secundários vinculados à conta (não inclui o principal). Cada item:{ email, verified }— pode incluir e-mails ainda não verificados (verified: false), adicionados pelo usuário mas com a confirmação pendente. Não trate como e-mail confiável sem checarverifiedpor item.avatar_url— URL pública da foto. String vazia se sem foto.verified_id—truese o usuário tem conta verificada e válida no Pangeia ID (selfie + documento aprovados por revisão manual, mediante pagamento anual).falsecaso contrário, inclusive se a verificação já expirou. Não confundir com overifiedde dentro de cada item deemails, que se refere só à verificação do endereço de e-mail.created_at/updated_at— ISO 8601 UTC com offset.
Como exibir o selo de conta verificada
Quando verified_id vier true, recomendamos indicar isso
visualmente com um selo azul sobreposto ao avatar do usuário, no canto
inferior direito — o mesmo padrão usado por redes sociais e apps de
mensageria pra indicar contas verificadas. É um símbolo que os usuários já reconhecem;
reaproveitar essa posição/cor pra outra coisa (ex: "premium", "online") só gera confusão.
← Exemplo de posicionamento correto: selo azul (#1D9BF0) no canto inferior
direito do avatar, com um contorno na cor de fundo do seu app pra se destacar.
Código do exemplo acima:
<div style="position: relative; width: 72px; height: 72px;">
<img src="avatar.jpg" alt="Avatar"
style="width:100%;height:100%;border-radius:50%;object-fit:cover;" />
<!-- Selo de conta verificada — só renderize se verified_id === true -->
<span style="
position: absolute;
bottom: -2px;
right: -2px;
width: 22px;
height: 22px;
background: #1D9BF0;
border-radius: 50%;
border: 2.5px solid white;
display: flex;
align-items: center;
justify-content: center;
">
<svg width="13" height="13" viewBox="0 0 24 24" fill="white">
<path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4z"/>
</svg>
</span>
</div>
- Cor:
#1D9BF0— o azul já associado a "verificado" em outras plataformas. - Posição: canto inferior direito do avatar, levemente sobreposto à borda, com um contorno branco (ou da cor de fundo do seu app) pra se destacar do que está atrás.
- Símbolo: um check (✓) dentro do círculo — simples e universalmente reconhecido.
- Quando mostrar: só quando
verified_id === trueno payload mais recente. Reaja ao webhookuser.updatedpra manter o selo em sincronia — ele dispara toda vez queverified_idmuda: aprovação, renovação, expiração sem renovação, ou remoção da verificação por um admin (nesses dois últimos casos, volta afalse).
Erros possíveis:
401 INVALID_INTEGRATOR_TOKEN— API Key ausente ou inválida400 INVALID_LOGIN_HASH— hash ausente ou não encontrada401 EXPIRED_LOGIN_HASH— hash expirada (validade: 30 minutos)409 LOGIN_HASH_ALREADY_CONSUMED— hash já usada403 USER_BLOCKED— conta bloqueada por um administrador do Pangeia ID
POST /api/user/by-pid
Body: { "pid": "pid_..." }
Retorna {"ok": true, "user": {...}} com o mesmo objeto user acima. Atualiza last_access_at do vínculo automaticamente.
Erros possíveis:
401 INVALID_INTEGRATOR_TOKEN— API Key ausente ou inválida400 INVALID_PID— campopidausente404 PID_NOT_FOUND_OR_REVOKED— PID não existe para esta API Key ou foi revogado
GET /api/identity-verification/price
Retorna o valor vigente da anuidade de verificação de conta — use pra informar seus próprios usuários de quanto custa a conta verificada, sem precisar hardcodar o valor no seu app (o preço pode mudar).
curl https://id.pangeialabs.com/api/identity-verification/price \
-H "Authorization: Bearer SUA_API_KEY"
Resposta:
{
"ok": true,
"price": 30.0,
"currency": "BRL",
"period": "year"
}
⚠️ price pode vir 0. A anuidade é configurável pelo Pangeia ID e pode ser zerada a qualquer momento. Não assuma um valor sempre maior que zero — trate esse caso na sua própria interface: se o seu app mostra algo como "custa apenas R$ {'{'}price{'}'}", troque a mensagem quando price === 0 em vez de exibir "custa R$ 0,00". Uma boa dica: em vez de mostrar o preço, aproveite pra deixar mais convidativo — algo como "e a boa notícia é que está 100% grátis agora! 🎉", com destaque e uma animação, chama bem mais atenção do que só "grátis".
Erros possíveis:
401 INVALID_INTEGRATOR_TOKEN— API Key ausente ou inválida
O que é o PID
O pid (persistent identifier) é o identificador único e permanente de um usuário dentro da sua integração. É gerado automaticamente no primeiro login e reutilizado em todos os logins seguintes do mesmo usuário no mesmo app.
Use o pid como chave primária do usuário na sua base de dados — ele nunca muda, mesmo que o usuário troque e-mail ou username.
Webhook (segurança e consumo)
Configure uma webhook_url na sua integração (aba API Keys do dashboard). O Pangeia enviará eventos assinados a cada mudança relevante no perfil do usuário.
Headers enviados pelo Pangeia
Content-Type: application/jsonX-Pangeia-Signature: sha256=<hmac_hex>— HMAC-SHA256 do body compacto com owebhook_secretX-Pangeia-Timestamp: <unix_seconds>— timestamp Unix da entrega (não do evento)X-Pangeia-Event-Id: evt_...— ID único do evento (use para idempotência)
Payload
{
"version": "2",
"event_id": "evt_...",
"event_type": "user.updated",
"timestamp": "2026-02-14T00:00:00Z",
"pid": "pid_...",
"user": {
"id": "...",
"name": "...",
"display_name": "...",
"username": "nome-de-usuario",
"aliases": ["nome-de-usuario-alt"],
"email": "principal@email.com",
"emails": [{ "email": "outro@email.com", "verified": true }],
"phone": "...",
"taxid": "...",
"birth_date": "...",
"avatar_url": "https://...",
"verified_id": false,
"created_at": "2025-01-10T00:00:00+00:00",
"updated_at": "2026-06-18T18:07:00+00:00"
}
}
Tipos de evento
user.updated— usuário alterou perfil (nome, e-mail, username, avatar, telefone, CPF, aliases etc.), ou o campoverified_idmudou: conta aprovada, renovada, expirada sem renovação, ou verificação removida por um admin. Inclui o caso do usuário apagar um campo opcional — veja "Campos vazios" abaixo. Também dispara quando o usuário adiciona ou remove um item dealiases— o payload sempre traz a lista completa e atual, não só o item alterado.user.revoked— usuário revogou o acesso da sua integração. Trate como "desconexão de conta", não como banimento — veja "Revogação e reconexão" abaixo.
Campos vazios ("") = o usuário apagou aquela informação
Telefone, CPF e data de nascimento são opcionais no perfil Pangeia ID — o usuário pode apagar qualquer um deles no dashboard dele. Quando isso acontece, o user.updated chega com o campo como string vazia ("") — o campo nunca é omitido do payload, e o Pangeia ID nunca envia null pra esses campos, sempre "". Exceção: enquanto o usuário tiver uma verificação de conta em andamento (documentos enviados, aguardando pagamento ou em revisão) ou aprovada e vigente, CPF e data de nascimento ficam travados e não podem ser apagados nem alterados — só o telefone continua livre nesse período.
Trate "" como "apague esse dado no seu sistema", não como "sem novidade, deixo como está". Se você ignorar um campo vazio, o dado que o usuário explicitamente removeu — o CPF que ele apagou, o telefone que ele tirou do perfil — continua guardado no seu banco, e isso quebra a expectativa dele de que remover ali remove em todo lugar que aceita login Pangeia ID.
A mesma regra vale pra qualquer alteração, em qualquer campo: se name, username, avatar_url etc. mudar, sobrescreva com o valor que veio no evento (ou na resposta mais recente de exchange-hash/by-pid) — nunca faça merge com o que você já tinha, nunca descarte por parecer uma mudança pequena. O Pangeia ID é a fonte de verdade do perfil; seu banco deve refletir exatamente o último payload recebido, campo a campo. Por robustez na sua checagem, trate null e "" como equivalentes mesmo que hoje só "" seja enviado.
Revogação e reconexão: o usuário pode sumir e depois voltar
Revogar (aba "Apps conectados" do dashboard do usuário) desconecta só aquela integração — a conta Pangeia ID dele continua existindo normalmente, com todas as outras integrações intactas. O pid daquele vínculo específico é marcado como revogado pra sempre: a partir do user.revoked, qualquer chamada a POST /api/user/by-pid com esse pid responde 404 PID_NOT_FOUND_OR_REVOKED permanentemente.
Ao receber user.revoked, seu sistema precisa fazer duas coisas ao mesmo tempo:
- Ocultar o usuário — ele não deve mais aparecer como logado nem conseguir acessar a conta vinculada àquele pid no seu site/app.
- Não impedir ele de voltar. Revogar não é um banimento. Se o mesmo usuário clicar em "Entrar com Pangeia ID" no seu site de novo no futuro, o login tem que funcionar normalmente.
O detalhe importante: esse novo login não reaproveita o pid revogado — o Pangeia ID detecta que o vínculo anterior está revogado e gera um pid novo pra esse mesmo par usuário+integração. Do ponto de vista do seu sistema, um pid novo é um usuário novo, sem ligação nenhuma com o pid antigo.
É por isso que a regra abaixo existe: se o usuário revogou o acesso, ele não pode logar de novo e cair direto na conta antiga como se nada tivesse acontecido — vendo pedidos, mensagens, preferências ou qualquer outro dado que ele já tinha "desconectado" antes. Pra garantir isso:
- Se a lei te obriga a guardar algum dado desse usuário mesmo depois da revogação (nota fiscal, registro contábil, obrigação da Receita Federal etc.), guarde — mas em um histórico/arquivo separado, não pendurado em nenhuma conta ativa e acessível pelo usuário.
- Nunca correlacione por fora do pid — por e-mail, CPF ou nome — pra "reconectar" o pid novo aos dados da conta antiga e mostrar aquilo de volta pro usuário. Mesmo que seja tecnicamente a mesma pessoa, pro seu sistema esse pid novo é uma conta nova, e é assim que ela deve ser tratada.
- O payload do próprio
user.revokedjá reflete o estado atual do perfil no momento da revogação — use-o pra arquivar o que for necessário antes de ocultar/apagar o registro ativo vinculado ao pid revogado.
Validação da assinatura
O HMAC é calculado sobre o body JSON compacto (sem espaços), usando o webhook_secret da integração.
import hmac, hashlib
from flask import request
def verify_signature(raw_body: bytes, signature_header: str, secret: str) -> bool:
if not signature_header or not signature_header.startswith("sha256="):
return False
sent = signature_header.split("=", 1)[1]
expected = hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(sent, expected)
@app.post("/webhook/pangeia-id")
def webhook():
raw = request.get_data()
sig = request.headers.get("X-Pangeia-Signature", "")
if not verify_signature(raw, sig, secret="SEU_WEBHOOK_SECRET"):
return {"ok": False, "error": "invalid signature"}, 401
event = request.get_json() or {}
event_id = event["event_id"] # use para idempotência
event_type = event["event_type"] # "user.updated" | "user.revoked"
pid = event["pid"]
user = event["user"]
# processar e retornar 200 rapidamente (o worker faz retry em falha)
return {"ok": True}, 200
Retry: o worker faz retentativas com backoff exponencial em caso de falha HTTP. Responda 2xx o mais rápido possível; processe de forma assíncrona se necessário.
Modos de abertura: popup vs modal
| Modo | Como abre | Quando usar |
|---|---|---|
popup (padrão) |
Nova janela do browser (window.open) |
Compatibilidade ampla, mas pode ser bloqueado em mobile ou por ad-blockers |
modal |
Overlay full-screen com iframe na mesma aba | Recomendado — não sofre bloqueio de popup, melhor UX em mobile |
Ativando o modo modal
Via atributo no botão:
<button class="pangeiaid-login" data-mode="modal" data-exchange-url="/auth/exchange-hash">
Entrar com Pangeia ID
</button>
Via JavaScript:
PangeiaID.openLogin({
mode: "modal",
exchange: {
enabled: true,
url: "/auth/exchange-hash",
},
onAuthenticated: function (result) {
// result = { ok, pid, user }
window.location.reload();
},
onCancel: function (info) {
// info.reason: ver tabela abaixo
console.log("Cancelado:", info.reason);
},
onError: function (err) {
// err.code + err.message + err.retriable
console.error("Erro:", err.code, err.message);
},
});
O modal fecha automaticamente após autenticação. O usuário também pode fechar clicando no ×, fora do modal ou pressionando Escape.
Abrindo direto na aba de cadastro
Por padrão o modal/popup abre na aba Entrar. Para abrir já na aba Criar conta — útil quando seu site tem botões separados de "Entrar" e "Criar conta grátis" — passe screen: "register". Sem essa opção, nada muda para quem já integra hoje.
<button class="pangeiaid-login" data-mode="modal" data-exchange-url="/auth/exchange-hash">
Entrar
</button>
<button class="pangeiaid-login" data-mode="modal" data-exchange-url="/auth/exchange-hash" data-screen="register">
Criar conta grátis
</button>
Via JavaScript, basta informar screen na chamada correspondente a cada botão:
document.getElementById("btn-entrar").addEventListener("click", function () {
PangeiaID.openLogin({ mode: "modal", screen: "login", exchange: { enabled: true, url: "/auth/exchange-hash" }, /* ... */ });
});
document.getElementById("btn-criar-conta").addEventListener("click", function () {
PangeiaID.openLogin({ mode: "modal", screen: "register", exchange: { enabled: true, url: "/auth/exchange-hash" }, /* ... */ });
});
CSP adicional para modo modal
Content-Security-Policy:
default-src 'self';
script-src 'self' https://id.pangeialabs.com;
connect-src 'self' https://id.pangeialabs.com;
frame-src https://id.pangeialabs.com;
base-uri 'self';
Referência do SDK
PangeiaID.openLogin(options)
Abre o fluxo de login programaticamente. Útil quando o auto-bind do botão não é suficiente.
exchange.enabled: true junto com exchange.url.
Desde a versão 2.1.2 do SDK, informar exchange.url já liga o exchange automaticamente mesmo sem enabled: true — mas escreva os dois campos explicitamente mesmo assim, é mais claro pra quem ler o código depois. Se por algum motivo exchange.url não chegar a ser configurado, o modo modal falha alto e visível (erro INVALID_CONFIG); o modo popup falha silenciosamente — o login completa no Pangeia ID e seu backend nunca é chamado, sem erro em lugar nenhum. Veja o exemplo completo logo abaixo.
| Opção | Tipo | Padrão | Descrição |
|---|---|---|---|
mode | string | "popup" | "popup" ou "modal" |
screen | string | "login" | Aba inicial: "login" ou "register" |
exchange.enabled | boolean | inferido de exchange.url | Liga o exchange server-to-server. Recomendado declarar true explicitamente por clareza, mas informar exchange.url já basta (desde a v2.1.2 do SDK). |
exchange.url | string | — | Endpoint local de exchange (obrigatório para usar exchange) |
exchange.method | string | "POST" | Método HTTP do exchange |
exchange.credentials | string | "include" | Política de cookies do fetch |
exchange.timeoutMs | number | 15000 | Timeout do exchange em ms |
exchange.headers | object | {"Content-Type":"application/json"} | Headers customizados para o exchange |
onAuthenticated(result) | function | — | Chamado com o objeto de resposta do exchange |
onCancel(info) | function | — | Chamado quando o usuário fecha sem autenticar |
onError(err) | function | — | Chamado em erros de configuração ou de rede |
Callback: onAuthenticated(result)
result é a resposta do seu endpoint de exchange — normalmente o objeto retornado pelo Pangeia:
{ ok: true, pid: "pid_...", user: { name, email, username, ... } }
Callback: onCancel(info)
info.reason | Quando ocorre |
|---|---|
user_closed | Usuário clicou no botão × do modal/popup |
escape | Usuário pressionou Escape |
backdrop_click | Usuário clicou fora do modal (modo modal) |
popup_closed | Usuário fechou a janela popup manualmente |
popup_blocked | Browser bloqueou a abertura do popup |
timeout | Tempo limite excedido sem autenticação |
already_open | Tentativa de abrir modal já aberto |
Callback: onError(err)
O objeto err tem os campos code, message, retriable e opcionalmente cause.
err.code | Causa |
|---|---|
INVALID_CONFIG | exchange.url ausente ou configuração inválida |
EXCHANGE_HTTP_ERROR | Seu endpoint de exchange retornou HTTP 4xx/5xx |
EXCHANGE_INVALID_RESPONSE | Resposta do exchange não contém "ok": true |
EXCHANGE_TIMEOUT | Timeout atingido na chamada ao exchange |
EXCHANGE_NETWORK_ERROR | Falha de rede na chamada ao exchange |
PANGEIA_MESSAGE_INVALID | Mensagem postMessage do Pangeia veio incompleta |
Eventos DOM
Além dos callbacks, o SDK dispara eventos no document — útil para ouvir em múltiplos pontos da página.
| Evento | ev.detail |
|---|---|
pangeia:authenticated | { element, result } — result é a resposta do exchange |
pangeia:cancel | { element, info } — info.reason conforme tabela acima |
pangeia:error | { element, error } — error.code conforme tabela acima |
element é o botão .pangeiaid-login que originou o fluxo (disponível apenas no auto-bind).
PangeiaID.init(options)
Chamado automaticamente no DOMContentLoaded. Pode ser chamado manualmente para configurar callbacks globais ou um seletor customizado.
PangeiaID.init({
selector: ".meu-botao-login", // padrão: ".pangeiaid-login"
mode: "modal", // padrão para todos os botões sem data-mode
exchange: {
url: "/auth/exchange-hash",
credentials: "include",
},
onAuthenticated: function (result, element) { /* ... */ },
onCancel: function (info, element) { /* ... */ },
onError: function (err, element) { /* ... */ },
});
Configuração por data-* (auto-bind)
Atributos disponíveis no elemento .pangeiaid-login:
| Atributo | Padrão | Descrição |
|---|---|---|
data-mode | popup | popup ou modal |
data-screen | login | Aba inicial: login ou register |
data-exchange-url | — | Endpoint local de exchange (obrigatório) |
data-exchange-method | POST | Método HTTP do exchange |
data-exchange-credentials | include | Política de cookies do fetch |
data-exchange-timeout-ms | 15000 | Timeout em ms |
data-exchange-payload-key | login_hash | Nome do campo enviado ao exchange |
data-exchange-headers | {"Content-Type":"application/json"} | JSON com headers customizados para o exchange |
FAQ
Popup ou modal — qual escolher?
Modal. Não sofre bloqueio de popup, funciona melhor em mobile e tem UX mais fluida. Popup fica disponível para casos que exijam janela separada.
Preciso expor a API Key no frontend?
Não. A API Key fica exclusivamente no backend. O frontend não carrega credenciais.
Como identificar o usuário no meu banco?
Use o pid. É persistente e único por usuário por integração — nunca muda mesmo que o usuário troque e-mail ou username.
Meu endpoint de exchange deve retornar exatamente o que o Pangeia retorna?
Sim — o SDK valida se a resposta contém "ok": true. Retornar o objeto inteiro do Pangeia é o caminho mais simples.
O webhook pode chegar mais de uma vez com o mesmo event_id?
Sim, em caso de retry. Sempre persista o event_id e ignore eventos já processados.
Como obter o webhook_secret?
Ele é gerado automaticamente ao criar a integração e exibido no dashboard em "API Keys". Você pode rotacioná-lo a qualquer momento.
Como sei se um usuário tem conta verificada?
Campo verified_id no objeto user (exchange-hash, by-pid e webhooks). Fica true só enquanto a verificação estiver aprovada e dentro da validade de 1 ano. O webhook user.updated dispara toda vez que esse campo muda — não só quando vira true (aprovação/renovação), mas também quando volta a false (expiração sem renovação, ou verificação removida por um admin).
O verified_id de um usuário voltou pra false sem ele ter deixado a verificação expirar — o que houve?
Um admin do Pangeia ID pode remover uma verificação manualmente (ex.: documento enviado por engano, suspeita de fraude, pedido do próprio usuário). Isso dispara user.updated igual a uma expiração normal — trate do mesmo jeito, sem tratamento especial: esconda o selo de verificado assim que verified_id vier false, independente do motivo.
Um campo do usuário chegou como "" — foi bug, ou o usuário apagou de propósito?
Ele apagou de propósito. Telefone, CPF e data de nascimento são opcionais e o usuário pode removê-los a qualquer momento — o campo vem como string vazia, nunca omitido, nunca null. Apague o dado correspondente no seu sistema também. Veja "Campos vazios" na seção de webhook.
Um usuário que revogou o acesso pode voltar a logar no meu site depois?
Sim, e o login funciona normalmente — revogar desconecta a integração, não bane a pessoa. Mas ele volta com um pid novo, nunca o revogado. Trate isso como um usuário novo, sem ligação com a conta antiga: qualquer dado que você tenha retido daquele pid revogado por obrigação legal precisa ficar num histórico isolado, nunca visível a partir do pid novo. Veja "Revogação e reconexão" na seção de webhook.
Checklist de go-live
- API Key salva em variável de ambiente no backend (nunca no frontend).
- Botão com
.pangeiaid-loginedata-exchange-urlfuncionando. - Endpoint local
/auth/exchange-hashcriado e testado. - Sessão local criada após exchange com sucesso (
pidsalvo). - Webhook configurado, assinatura validada e idempotência por
event_idimplementada. - Campo vazio (
"") emuser.updatedé tratado como remoção do dado no seu sistema, não ignorado. user.revokedoculta o usuário sem apagar dados de guarda legal obrigatória — e, se ele voltar com um pid novo, esses dados retidos não resurgem na conta dele.- CSP configurada (adicionar
frame-srcse usar modo modal).
Integrando com ajuda de IA
Se você usa IA para programar (Cursor, GitHub Copilot, Claude, ChatGPT etc.), o fluxo é simples: você faz a parte do dashboard manualmente (leva 2 minutos) e passa um prompt pronto para a IA implementar todo o resto.
Passo 1 — Prepare o dashboard (você faz, não a IA)
- Acesse seu dashboard e vá para a aba API Keys.
- Ative o Modo Desenvolvedor no canto superior direito da seção.
- Clique em Gerar nova API Key, informe o nome do seu app e salve.
- Copie o valor da API Key (começa com
pk_live_) — vai para uma variável de ambiente no seu backend. - Copie o Webhook Secret (começa com
whsec_) — também vai para variável de ambiente. - No campo URL do Webhook, informe o endpoint do seu backend que vai receber os eventos do Pangeia (ex:
https://seusite.com.br/webhooks/pangeia). Pode deixar em branco agora e preencher depois. - Salve a integração.
Passo 2 — Passe este prompt para a sua IA
Substitua os valores em caixa alta e cole direto no chat da sua IA:
Preciso integrar o Pangeia ID (SSO brasileiro) no meu projeto.
Leia a documentação em https://id.pangeialabs.com/apidoc e implemente a integração completa.
Minhas credenciais (use SOMENTE no backend, nunca no frontend ou no código versionado):
- API Key: pk_live_COLE_AQUI
- Webhook Secret: whsec_COLE_AQUI
- URL do meu webhook: https://MEU_SITE/MEU_ENDPOINT_WEBHOOK