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.1.2 — 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 segue o shape:
{
"ok": false,
"code": "NOME_DO_ERRO",
"message": "Descrição legível do problema."
}
POST /api/auth/exchange-hash
Body: { "login_hash": "temp_..." }
Resposta de sucesso:
{
"ok": true,
"pid": "pid_...",
"user": {
"id": "...",
"name": "...",
"username": "nome-de-usuario",
"email": "principal@email.com",
"emails": [{ "email": "outro@email.com", "verified": true }],
"phone": "...",
"taxid": "...",
"birth_date": "...",
"avatar_url": "https://...",
"created_at": "2025-01-10T00:00:00+00:00",
"updated_at": "2026-06-18T18:07:00+00:00"
}
}
Campos do objeto user:
username— slug único (ex:"joao-silva"). String vazia se ainda não definido.email— e-mail principal verificado. Use como identificador de e-mail.emails— e-mails secundários verificados vinculados à conta (não inclui o principal). Cada item:{ email, verified }.avatar_url— URL pública da foto. String vazia se sem foto.created_at/updated_at— ISO 8601 UTC com offset.
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á usada500 INTERNAL_ERROR
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
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": "...",
"username": "nome-de-usuario",
"email": "principal@email.com",
"emails": [{ "email": "outro@email.com", "verified": true }],
"phone": "...",
"taxid": "...",
"birth_date": "...",
"avatar_url": "https://...",
"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 etc.).user.revoked— usuário revogou o acesso da sua integração. Trate como "desconexão de conta".
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.
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" |
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-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.
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. - 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