Webhooks
Receba um POST HTTP assinado quando pessoas entram, contas mudam ou um administrador altera um realm, e confirme que ele veio do Authio.
Um webhook é um POST que o Authio envia para um endereço seu quando algo acontece, para que os seus sistemas reajam sem consultar a API: criar o usuário no seu banco quando a conta nasce, abrir um chamado quando um refresh token é reutilizado, espelhar cada mudança administrativa num repositório de compliance.
Toda entrega leva o mesmo envelope JSON e uma assinatura HMAC-SHA256 feita com um segredo que só você e o Authio conhecem. Entregas que falham são reenviadas no ritmo que você escolher, e cada tentativa fica num histórico de entregas que você lê, filtra e reenvia pelo console.
Endpoints de client e de realm
Um endpoint é cadastrado em um de dois lugares, e isso decide o que ele pode ouvir:
| Cadastrado em | O que ouve | Onde fica no console |
|---|---|---|
| Um client | O que acontece com as pessoas que usam aquela aplicação: logins, contas, tokens, chaves e suspensões. | A aba Atividade do client, ou Webhooks com o client escolhido |
| Um realm | Toda mudança que um administrador faz em qualquer lugar do realm: admin_resource_created, admin_resource_updated e admin_resource_deleted. | A aba Atividade do realm, em Trilha administrativa |
Os dois conjuntos não se misturam: um endpoint de client não pode assinar os eventos administrativos, e um endpoint de realm só pode assinar esses. Um endpoint que nomeia um evento que nunca receberia é recusado no cadastro, em vez de ficar mudo.
Cadastre um endpoint
Abra os webhooks do client
Descreva o endpoint
Escolha Adicionar endpoint e preencha um nome (até 128 caracteres), a URL (um endereço
https://absoluto com host, até 2.048 caracteres), uma descrição opcional e pelo menos um evento. Retentativas e Primeira retentativa após definem como as entregas que falham são reenviadas; os valores padrão servem para a maioria dos receptores.Guarde o segredo de assinatura
O Authio gera o segredo de assinatura do endpoint, um valor que começa com
whsec_. Coloque-o na configuração do seu receptor, por exemplo comoAUTHIO_WEBHOOK_SECRET.O segredo aparece uma única vez, logo depois que o endpoint é criado, e só para quem tem a permissãosecret:read. Depois disso o console mostra apenas os quatro últimos caracteres; se perder o segredo, escolha Rotacionar secret.Envie um teste
Escolha Enviar teste. O Authio envia um evento
pingao endpoint e registra a tentativa no histórico, com o código de status e o corpo que o seu receptor respondeu.
Tipos de evento
| Evento | Endpoint | Enviado quando |
|---|---|---|
user_created | Client | Um usuário é criado por qualquer caminho: cadastro, um administrador, uma importação ou o primeiro login por um provedor de identidade externo. |
user_updated | Client | O perfil ou o status de um usuário muda. |
user_deleted | Client | Um administrador exclui um usuário. data traz a conta como ela estava no momento da exclusão. |
login_succeeded | Client | Uma autenticação dá certo, em qualquer grant. |
login_failed | Client | Uma autenticação é recusada. |
user_locked_out | Client | Uma conta é bloqueada depois de tentativas que falharam seguidas. |
token_revoked | Client | Um token é revogado explicitamente. |
keys_rotated | Client | As chaves de assinatura e de criptografia do client são giradas, por um administrador ou pela rotação agendada. |
token_reuse_detected | Client | Um refresh token é apresentado de novo depois de já ter sido trocado. A sessão por trás dele foi encerrada. |
user_suspended | Client | Uma suspensão é registrada, comece ela agora ou depois. |
user_suspension_ended | Client | Uma suspensão é levantada ou chega ao fim. |
admin_resource_created | Realm | Um administrador cria um recurso: um client, um papel, uma chave, qualquer coisa. |
admin_resource_updated | Realm | Um administrador altera um recurso. |
admin_resource_deleted | Realm | Um administrador exclui um recurso. |
ping | Ambos | Você escolhe Enviar teste. Não pode ser assinado. |
Só mudanças administrativas que deram certo são anunciadas; tentativas recusadas ficam na trilha de auditoria. Trocas de token de contas conectadas são registradas na trilha de acessos, mas não disparam login_succeeded nem login_failed, porque não são logins.
Payload
Todo evento chega no mesmo envelope. id identifica o evento e é o mesmo em todas as retentativas e reenvios dele; created_at vem em segundos Unix; realm_id e client_id dizem onde ele aconteceu; data depende do evento.
{
"id": "6c1e0f7a-3b9d-4f2e-9a51-0d8c2b7e4f13",
"type": "login_succeeded",
"created_at": 1791360000,
"realm_id": "acme",
"client_id": "acme-web",
"data": {
"grant_type": "AuthorizationCode",
"user_id": "0f6b2c4e-8a1d-4c3b-9e7f-5a2d1c8b6e40",
"username": "ana",
"email": "ana@acme.com",
"ip_address": "203.0.113.24",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
"session_id": "b7d4e2a1-5c3f-4e8b-a6d9-1f0c7e3b2a58",
"failure_reason": null,
"resource": null,
"scopes": "openid profile email",
"authentication_methods": ["pwd", "otp", "mfa"],
"acr": "2",
"identity_provider": null,
"consumer_id": null,
"consumer_name": null
}
}O que cada evento leva em data
| Eventos | Campos em data |
|---|---|
user_created, user_updated, user_deleted | id, username, email, first_name, last_name, email_confirmed, status, realm_id, client_id |
login_succeeded, login_failed | grant_type, user_id, username, email, ip_address, user_agent, session_id, failure_reason, resource, scopes, authentication_methods, acr, identity_provider, consumer_id, consumer_name |
user_locked_out | user_id, username, email, lockout_end, access_failed_count |
token_revoked | token_id, subject, session_id, token_type_hint |
token_reuse_detected | token_id, subject, session_id, ip_address, user_agent |
keys_rotated | signing_key_id, encryption_key_id, mais revoked_keys_count numa rotação manual, ou rotation_days e automatic numa rotação agendada |
user_suspended, user_suspension_ended | id, user_id, level, status, starts_at, ends_at, restricted_scopes, revokes_every_role, revoked_role_ids, sessions_terminated_at, lifted_at |
admin_resource_created, admin_resource_updated, admin_resource_deleted | action, resource_type, resource_id, parent_id, realm_id, client_id, actor_type, actor_user_id, actor_username, actor_email, actor_credential_id, actor_credential_name, ip_address, method, path, mais details numa rotação de client secret |
ping | message, subscription |
resource separa um login na sua aplicação de um token emitido para um servidor de recurso, como um servidor MCP, e authentication_methods, acr e identity_provider dizem como a pessoa entrou. Nos eventos administrativos o tipo de recurso vai em resource_type, para que os três nomes de evento não mudem quando a API cresce.
resource_idé o que foi criado, alterado ou excluído, eparent_idaquilo a que ele pertence, quando o endpoint o nomeia (a pessoa de uma suspensão, o provedor de identidade de um mapeamento de papel); os dois vêmnullquando não há o que nomear, como numa importação que mexe em vários registros de uma vez.actor_typediz quem fez a mudança:User, uma pessoa logada, ouCredential, uma credencial da conta (chave de API, token de acesso pessoal ou segredo de cliente). Para uma credencial,actor_credential_ideactor_credential_namea nomeiam,actor_user_ideactor_usernamenomeiam a pessoa em nome de quem ela age, eactor_emailvemnull.- A rotação do segredo de um client (
admin_resource_updated) também trazdetails:mode—Immediate(o segredo anterior foi recusado na hora),Overlap(ele ainda funciona por um tempo) ouCompromise(ele foi recusado e os tokens emitidos com ele, revogados) — comprevious_value_valid_untileissued_tokens_revoked_before, em UTC, nos modos a que pertencem. Veja Rotação de segredos. - Chamadas que não mudam nada não publicam nada: o teste enviado a um webhook, a reentrega de uma entrega, a prévia de projeção de token ou de mapeador de claims, o reenvio de um e-mail de confirmação e todo ensaio (
dryRun) de uma importação. Uma mudança recusada, uma personificação e uma mudança fora de um realm também não publicam nada.
Cabeçalhos
| Cabeçalho | Valor |
|---|---|
X-Authio-Signature | t={timestamp},v1={signature}: o horário Unix em que a tentativa foi enviada e o HMAC-SHA256 de {timestamp}.{body} em hexadecimal minúsculo. |
X-Authio-Event | O nome do evento, ou ping, para você rotear sem ler o corpo. |
X-Authio-Event-Id | O id do evento, o mesmo em toda retentativa e todo reenvio. |
Content-Type | application/json |
Valide as assinaturas
Qualquer um que conheça o endereço do seu endpoint consegue enviar algo para ele, então confira cada entrega antes de agir:
- Leia o corpo bruto da requisição, byte a byte, antes de qualquer parse de JSON.
- Separe o
X-Authio-Signaturepelas vírgulas e pegue os valores detev1. - Calcule o HMAC-SHA256 de
{t}.{corpo bruto}usando como chave o segredo inteiro, com o prefixowhsec_, e codifique em hexadecimal minúsculo. - Compare o resultado com
v1em tempo constante. - Recuse a entrega se
testiver a mais de cinco minutos do seu relógio. O horário é definido no envio de cada tentativa, então retentativas e reenvios chegam com um horário novo.
import { createHmac, timingSafeEqual } from 'node:crypto'
const TOLERANCE_SECONDS = 5 * 60
export function verifyAuthioSignature(
payload: string,
header: string | null,
secret: string,
): boolean {
if (!header) return false
let timestamp = ''
let signature = ''
for (const part of header.split(',')) {
const [name, value = ''] = part.trim().split('=', 2)
if (name === 't') timestamp = value
if (name === 'v1') signature = value
}
if (!/^\d+$/.test(timestamp) || !/^[0-9a-f]{64}$/.test(signature)) {
return false
}
const age = Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp))
if (age > TOLERANCE_SECONDS) return false
const expected = createHmac('sha256', secret)
.update(`${timestamp}.${payload}`)
.digest()
return timingSafeEqual(expected, Buffer.from(signature, 'hex'))
}Depois, no seu handler, leia o corpo como texto, valide e responda rápido:
import { verifyAuthioSignature } from '@/lib/authio-webhook'
interface AuthioEvent {
id: string
type: string
created_at: number
realm_id: string | null
client_id: string | null
data: Record<string, unknown>
}
export async function POST(request: Request) {
const payload = await request.text()
const valid = verifyAuthioSignature(
payload,
request.headers.get('x-authio-signature'),
process.env.AUTHIO_WEBHOOK_SECRET!,
)
if (!valid) {
return new Response('Invalid signature', { status: 400 })
}
const event = JSON.parse(payload) as AuthioEvent
console.log(`Received ${event.type} (${event.id})`)
return new Response(null, { status: 204 })
}X-Authio-Signature, e o console recalcula a assinatura no seu navegador. Nada do que você cola ali é enviado a lugar nenhum.Retentativas e histórico de entregas
- Uma tentativa dá certo quando o seu endpoint responde com qualquer status
2xxem até 10 segundos. Qualquer outro status, um tempo esgotado ou um erro de conexão conta como falha. Redirecionamentos não são seguidos. - Um evento que falhou é reenviado conforme a política do endpoint. Com os padrões (5 retentativas, a primeira depois de 60 segundos), elas chegam cerca de 1 minuto, 5 minutos, 25 minutos, 2 horas e 6 horas depois de cada falha: cada uma espera cinco vezes mais que a anterior, nunca mais de seis horas, com variação de até 20% para mais ou para menos.
- Retentativas aceita de 0 a 10 (0 desliga) e Primeira retentativa após, de 30 a 3.600 segundos. Testes e reenvios manuais nunca são repetidos automaticamente.
- Depois de 10 falhas seguidas o endpoint é desativado automaticamente; num endpoint de client, o dono da conta também recebe uma notificação crítica. Um sucesso zera a contagem, e as retentativas automáticas não somam nela. Retentativas já agendadas continuam saindo com o endpoint pausado ou desativado.
Um endpoint fica Active, Paused ou Disabled. Para trazer de volta um endpoint de realm desativado, corrija o receptor e ligue de novo Recebendo no editor dele. O console cadastra, testa, rotaciona o segredo e exclui endpoints de client; para alterar um deles, ou voltar um desativado para Active, atualize-o pela Admin API com PUT /api/v1/admin/realms/{realm}/clients/{client}/webhooks/{webhookId}.
O histórico dos endpoints de um client fica em Webhooks e na aba Atividade do client. Visão geral mostra tentativas, taxa de aceitação, latência e as respostas de falha mais comuns; Eventos lista cada ocorrência e até onde chegou o envio a todos os endpoints (Pending, Delivered, Partial ou Failed); Entregas lista cada tentativa com o corpo enviado e a sua resposta. Cada tentativa traz o seu tipo:
| Tipo | O que é |
|---|---|
Event | A primeira entrega de um evento. |
Retry | Uma retentativa automática de um evento que falhou. |
Replay | Uma entrega que alguém reenviou à mão. |
Test | Um ping enviado com Enviar teste. |
Reenviar esta entrega manda de novo o payload guardado, sem alteração, mesmo que o usuário ou o recurso descrito tenha mudado desde então. Só funciona com o endpoint ativo e cancela as retentativas automáticas ainda agendadas para o mesmo evento. Nos endpoints de realm o console lista as entregas e envia testes; reenvio e métricas existem nos endpoints de client. O histórico de entregas é guardado por 30 dias.
Teste o seu endpoint
- Enviar teste manda um
pingassinado para um endpoint. Ele conta na saúde do endpoint, porque um endpoint que recusa um teste está com um problema de verdade. - Endpoint de captura, em Webhooks, abre um endereço temporário que registra cada requisição recebida — método, cabeçalhos e corpo — por 24 horas. Use-o como URL de um endpoint para ver exatamente o que o Authio envia antes de o seu receptor existir. O endereço é protegido só pelo caminho aleatório, então envie apenas dados de teste para ele.
- O Authio só entrega em endereços públicos, conferidos depois da resolução de DNS:
localhoste redes privadas são recusados. Para receber na sua máquina, use o endpoint de captura ou um túnel que dê a você um endereço HTTPS público.
Boas práticas
- Valide antes de agir. Recuse toda entrega cuja assinatura não confere ou cujo horário está fora da sua tolerância.
- Responda rápido, processe depois. Devolva um
2xxassim que a entrega for validada e guardada, e faça o trabalho demorado numa fila. Dez segundos é o limite, e um endpoint lento faz falhar entregas que ele de fato recebeu. - Torne os handlers idempotentes. A entrega é pelo menos uma vez: o mesmo evento pode chegar de novo por uma retentativa ou um reenvio. Guarde o
id(também emX-Authio-Event-Id) do que já processou e ignore as repetições. - Não dependa da ordem. Uma retentativa pode chegar horas depois de um evento mais novo. Use
created_at, ou leia o estado atual pela API, quando a ordem importar. - Trate `token_reuse_detected` como incidente. Ele indica que um refresh token muito provavelmente foi roubado: alguém além do titular o apresentou.
- Planeje a rotação do segredo. Rotacionar secret invalida o segredo anterior na hora, sem período de sobreposição, então atualize o receptor em seguida. As entregas que falharem nesse intervalo são reenviadas conforme a política do endpoint, já assinadas com o segredo novo.
Webhooks de notificação
À parte dos webhooks de eventos, as notificações operacionais da sua conta — segredos vencendo, falhas de login fora do comum, um webhook desativado, cotas perto do limite — também podem ser enviadas para uma URL. O dono da conta define isso em Notificações › Preferências. O corpo traz id, type, severity, title, description, resource_id, realm_id e created_at. Esses envios não são assinados e não aparecem no histórico de entregas, então trate-os como alertas, não como fonte da verdade.