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.

🤖
Usa IA para programar? Cursor, Copilot, Claude, ChatGPT — tem um prompt pronto esperando por você. Pular direto para a seção de integração via IA →

Antes de começar: obtenha sua API Key

Toda chamada server-to-server ao Pangeia ID requer uma API Key. Para obtê-la:

  1. Acesse seu dashboard Pangeia ID e vá para a aba API Keys.
  2. Ative o Modo Desenvolvedor no canto superior direito da seção.
  3. Clique em Gerar nova API Key e informe o nome do seu app.
  4. 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)

  1. Adicione o script pangeia-id.js na sua página.
  2. Crie um botão com classe .pangeiaid-login e data-exchange-url.
  3. Implemente no seu backend o endpoint POST /auth/exchange-hash.
  4. Seu backend chama POST /api/auth/exchange-hash no Pangeia com sua API Key.
  5. Com pid + user na 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:

Erros possíveis:

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:


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

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

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

ModoComo abreQuando 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.

💡
Sempre inclua 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çãoTipoPadrãoDescrição
modestring"popup""popup" ou "modal"
exchange.enabledbooleaninferido de exchange.urlLiga 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.urlstringEndpoint local de exchange (obrigatório para usar exchange)
exchange.methodstring"POST"Método HTTP do exchange
exchange.credentialsstring"include"Política de cookies do fetch
exchange.timeoutMsnumber15000Timeout do exchange em ms
exchange.headersobject{"Content-Type":"application/json"}Headers customizados para o exchange
onAuthenticated(result)functionChamado com o objeto de resposta do exchange
onCancel(info)functionChamado quando o usuário fecha sem autenticar
onError(err)functionChamado 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.reasonQuando ocorre
user_closedUsuário clicou no botão × do modal/popup
escapeUsuário pressionou Escape
backdrop_clickUsuário clicou fora do modal (modo modal)
popup_closedUsuário fechou a janela popup manualmente
popup_blockedBrowser bloqueou a abertura do popup
timeoutTempo limite excedido sem autenticação
already_openTentativa de abrir modal já aberto

Callback: onError(err)

O objeto err tem os campos code, message, retriable e opcionalmente cause.

err.codeCausa
INVALID_CONFIGexchange.url ausente ou configuração inválida
EXCHANGE_HTTP_ERRORSeu endpoint de exchange retornou HTTP 4xx/5xx
EXCHANGE_INVALID_RESPONSEResposta do exchange não contém "ok": true
EXCHANGE_TIMEOUTTimeout atingido na chamada ao exchange
EXCHANGE_NETWORK_ERRORFalha de rede na chamada ao exchange
PANGEIA_MESSAGE_INVALIDMensagem 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.

Eventoev.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:

AtributoPadrãoDescrição
data-modepopuppopup ou modal
data-exchange-urlEndpoint local de exchange (obrigatório)
data-exchange-methodPOSTMétodo HTTP do exchange
data-exchange-credentialsincludePolítica de cookies do fetch
data-exchange-timeout-ms15000Timeout em ms
data-exchange-payload-keylogin_hashNome 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

  1. API Key salva em variável de ambiente no backend (nunca no frontend).
  2. Botão com .pangeiaid-login e data-exchange-url funcionando.
  3. Endpoint local /auth/exchange-hash criado e testado.
  4. Sessão local criada após exchange com sucesso (pid salvo).
  5. Webhook configurado, assinatura validada e idempotência por event_id implementada.
  6. CSP configurada (adicionar frame-src se 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)

  1. Acesse seu dashboard e vá para a aba API Keys.
  2. Ative o Modo Desenvolvedor no canto superior direito da seção.
  3. Clique em Gerar nova API Key, informe o nome do seu app e salve.
  4. Copie o valor da API Key (começa com pk_live_) — vai para uma variável de ambiente no seu backend.
  5. Copie o Webhook Secret (começa com whsec_) — também vai para variável de ambiente.
  6. 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.
  7. 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