Webhooks

Guia de Integração de Webhooks

Receba notificações em tempo real sobre eventos de controle de acesso — criação, atualização, transferência, cancelamento, reembolso e uso de ingressos.

Visão geral

Webhooks permitem que seu sistema seja notificado automaticamente sempre que um evento relevante ocorre na plataforma de controle de acesso da Ingresse. Em vez de consultar a API repetidamente (polling), você registra uma URL e passa a receber requisições HTTP POST com o payload do evento.

Como funciona

Você cria uma assinatura (subscription) informando sua URL e os gatilhos desejados. A Ingresse envia um POST assinado para essa URL sempre que um dos eventos ocorre.

Ambientes

AmbienteBase URLUso
production
<WEBHOOK_PROD_HOST>Fornecido junto com suas credenciais no onboarding.
development
https://awa-access-webhook.dev.ingresse.comAmbiente de testes/homologação.

Gatilhos disponíveis

Ao criar uma assinatura, escolha um ou mais gatilhos. Cada gatilho corresponde a um tipo de evento.

Gatilhoevent_typeDescrição
create
code_createdNovo código/ingresso criado.
update
code_updatedCódigo/ingresso atualizado.
transfer
code_transferredIngresso transferido para outra pessoa.
cancel
code_cancelledIngresso cancelado.
refund
code_refundedIngresso reembolsado.
use
code_usedIngresso utilizado (check-in/validação).

Disponibilidade dos gatilhos

A disponibilidade de cada gatilho para sua integração é confirmada durante o onboarding.

Estrutura do payload

Cada notificação é enviada como POST com corpo JSON. Os campos de topo identificam o evento; payload contém os dados do código/ingresso.

JSON
{
  "id": 1042,
  "event_type": "code_created",
  "created_at": "2026-04-29T13:00:12Z",
  "payload": {
    "attributes": {
      "attendee_external_ref": "a7e1c2d4-0000-4000-8000-000000000009",
      "locale": "pt-BR",
      "original_price_cents": 20000,
      "rfc": false
    },
    "buyer_id": "750e8400-e29b-41d4-a716-446655440000",
    "can_transfer": true,
    "code": "25ABC12345678",
    "created_at": "2026-04-29T13:00:11Z",
    "event": {
      "banner_image_url": "https://cdn.example.com/evento/banner.jpg",
      "external_id": "95a447c4-0000-4000-8000-000000000002",
      "id": "850e8400-e29b-41d4-a716-446655440000",
      "map_image_url": "https://cdn.example.com/evento/mapa.jpg",
      "name": "Evento de Exemplo 2026",
      "organization_id": "b10e8400-e29b-41d4-a716-446655440000",
      "place_id": "c20e8400-e29b-41d4-a716-446655440000",
      "slug": "evento-de-exemplo-2026",
      "status": "published",
      "timezone": "America/Sao_Paulo",
      "type": "party"
    },
    "event_id": "850e8400-e29b-41d4-a716-446655440000",
    "external_id": "ext-code-123",
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "order_id": "650e8400-e29b-41d4-a716-446655440000",
    "owner": {
      "birthdate": "1990-01-01",
      "document": "00000000000",
      "document_type": "cpf",
      "email": "[email protected]",
      "external_id": "ext-user-4471",
      "id": "750e8400-e29b-41d4-a716-446655440000",
      "name": "Nome Exemplo",
      "phone": "+5511900000000"
    },
    "owner_id": "750e8400-e29b-41d4-a716-446655440000",
    "ownership": "owner",
    "status": "active",
    "ticket": {
      "allotment": "Lote 1",
      "allotment_number": 1,
      "description": "Ingresso de exemplo",
      "external_id": "45b2c9d6-0000-4000-8000-000000000003",
      "group": {
        "entrance_mapping": "A1-A5",
        "entrance_method": "qrcode",
        "entrance_name": "Portao A",
        "event_dates": [
          {
            "date": "2026-09-09T22:30:00.000Z",
            "event_id": "850e8400-e29b-41d4-a716-446655440000",
            "external_id": "e91f2a06-0000-4000-8000-000000000005",
            "id": "c70e8400-e29b-41d4-a716-446655440000",
            "past": false
          }
        ],
        "external_id": "7c3d1e58-0000-4000-8000-000000000004",
        "id": "b60e8400-e29b-41d4-a716-446655440000",
        "name": "Pista",
        "order": 1
      },
      "group_id": "b60e8400-e29b-41d4-a716-446655440000",
      "holder_type": "all",
      "id": "950e8400-e29b-41d4-a716-446655440000",
      "name": "Inteira",
      "order": 0,
      "price": 20000,
      "transferable": "always"
    },
    "ticket_id": "950e8400-e29b-41d4-a716-446655440000",
    "type": "individual",
    "updated_at": "2026-04-29T13:00:11Z",
    "user": {
      "birthdate": "1990-01-01",
      "document": "00000000000",
      "document_type": "cpf",
      "email": "[email protected]",
      "external_id": "ext-user-4471",
      "id": "750e8400-e29b-41d4-a716-446655440000",
      "name": "Nome Exemplo",
      "phone": "+5511900000000"
    },
    "user_id": "750e8400-e29b-41d4-a716-446655440000"
  }
}
event_typeGatilhoO que muda em relação a code_created
code_created
createFormato base: status active, user, owner e buyer_id costumam ser a mesma pessoa.
code_updated
updateMesmo formato, com o estado novo do código — ex.: attributes.rfc após associar a biometria facial, ou ticket/group após troca de setor.
code_transferred
transferuser, owner, user_id e owner_id passam a ser o destinatário. buyer_id continua sendo quem comprou. ownership é recalculado.
code_cancelled
cancelstatus cancelled e can_transfer false. Em cortesias, user/owner podem vir ausentes.
code_refunded
refundstatus refunded e can_transfer false.
code_used
useÚnico com validation_metadata (sessão, tipo de entrada e horário). status continua active.
CampoTipoDescrição
id
intIdentificador da notificação.
event_type
stringTipo do evento (ex: code_created).
created_at
string (ISO 8601)Momento em que a plataforma ingeriu o evento (não necessariamente o instante exato em que ele ocorreu).
payload
objectDados do código/ingresso afetado.

Objeto payload

CampoTipoDescrição
id
string (UUID)ID interno do código de acesso.
code
stringValor do código apresentado na entrada.
external_id
stringIdentificador do código no sistema de origem. Opcional.
type
stringindividual | passport
status
stringactive | refunded | cancelled — não muda para used após o check-in.
order_id
string (UUID)Pedido de origem do código.
user_id
string (UUID)Igual a user.id. Opcional.
owner_id
string (UUID)Igual a owner.id. Opcional.
buyer_id
string (UUID)Comprador. Não existe objeto buyer no payload.
ownership
stringowner | other | dependent — pode vir ausente.
event_id
string (UUID)ID interno do evento — igual a event.id. Não é o UUID das URLs da Access Control.
ticket_id
string (UUID)ID interno do ingresso — igual a ticket.id. Vem vazio ("") em códigos de produto.
can_transfer
booleanSe o ingresso ainda pode ser transferido.
created_at
string (RFC3339)Criação do código. Não é o created_at do envelope.
updated_at
string (RFC3339)Última atualização do código. Opcional.
attributes
objectAtributos adicionais (ver abaixo). Opcional.
user
objectUsuário associado ao código (ver abaixo). Opcional.
owner
objectPortador do código (ver abaixo). Opcional.
event
objectDados do evento (ver abaixo). Opcional.
ticket
objectDados do ingresso (ver abaixo). Ausente em códigos de produto.
validation_metadata
objectDados do check-in (ver abaixo). Presente apenas em code_used.

Os campos de topo acima são um conjunto fechado. Já os objetos aninhados (event, ticket, user, owner, attributes e validation_metadata) são repassados como vêm da origem — podem ganhar campos novos sem aviso. Ignore campos desconhecidos ao processar.

Os objetos aninhados têm os mesmos nomes da Guest List API, mas não são os mesmos: campos e unidades diferem. Use as tabelas abaixo como referência do webhook.

Atenção aos identificadores: o event_id de topo e event.id são o mesmo valor — o ID interno do evento. O UUID usado nas URLs da Access Control API vem em event.external_id. A mesma regra vale para o ingresso: ticket_id de topo é igual a ticket.id (interno) e o identificador externo vem em ticket.external_id. Para correlacionar com chamadas à Access Control API, use sempre os campos external_id.

Objeto payload.event

CampoTipoDescrição
id
string (UUID)ID interno do evento — não é o UUID das URLs da Access Control.
external_id
string (UUID)UUID do evento — usado como :event_id nas URLs da Access Control.
name
string
slug
string
type
stringCategoria do evento (ex.: soccer_game). Não é um conjunto fechado.
status
stringdraft | published | finished | suspended
timezone
stringFuso horário do evento (IANA).
organization_id
string (UUID)
place_id
string (UUID)
provider_id
string (UUID)Opcional.
banner_image_url
string
map_image_url
string
event_dates
arrayOpcional — nem sempre vem preenchido. Todas as sessões do evento, em ordem cronológica. Mesma forma da tabela abaixo, mas outro formato de data. Para as sessões do setor use ticket.group.event_dates[].

Objeto payload.ticket

CampoTipoDescrição
id
string (UUID)ID interno do tipo de ingresso — igual ao ticket_id de topo.
external_id
stringIdentificador externo do tipo de ingresso.
group_id
string (UUID)Igual a group.id.
name
string
description
stringPode conter HTML. Opcional.
allotment
stringLote.
allotment_number
int
price
numberPreço em centavos (ex.: 20000). Na Guest List, price vem em reais.
transferable
stringalways | on_purchase | on_wallet | never
holder_type
stringall | user_only | dependent — pode vir ausente.
order
int
group
objectSetor do ingresso (ver abaixo).

Objeto payload.ticket.group

CampoTipoDescrição
id
string (UUID)ID interno do setor.
external_id
string (UUID)Identificador externo do setor.
name
string
order
int
entrance_method
stringface | qrcode
entrance_name
stringNome do ponto de entrada. Opcional.
entrance_mapping
stringOpcional.
attributes
objectAtributos do setor, mapa chave-valor. Opcional.
event_dates
arraySessões a que o setor dá acesso — subconjunto de event.event_dates, sem ordem garantida (ver abaixo).

Objeto payload.ticket.group.event_dates[]

CampoTipoDescrição
id
string (UUID)ID interno da sessão. É o valor de validation_metadata.session_id.
external_id
string (UUID)UUID da sessão — usado como :session_id na Validation API.
date
stringData e hora da sessão. Os dois níveis usam formatos diferentes: 2026-09-09T22:30:00.000Z aqui e 2026-09-09 22:30:00 em event.event_dates[]. Trate como string, não como RFC 3339.
event_id
string (UUID)ID interno do evento ao qual a sessão pertence.
past
booleantrue quando a sessão já ocorreu.

Onde está a sessão

A sessão vem em ticket.group.event_dates[], presente em todo código de ingresso. Em code_used, validation_metadata.session_id aponta a sessão do check-in pelo id interno — cruze com o array e leia o external_id para obter o UUID que a Validation API espera. Não confunda com ticket.event_dates da Guest List, que não é preenchido.

Objeto payload.user / payload.owner

CampoTipoDescrição
id
string (UUID)Igual a user_id / owner_id de topo.
external_id
stringIdentificador da pessoa no sistema de origem.
name
string
email
string
phone
string
document
stringDocumento do titular — CPF quando document_type = cpf.
document_type
stringPode vir vazio ("") quando o tipo não é conhecido.
birthdate
string (YYYY-MM-DD)

Dados pessoais (LGPD)

Os objetos user e owner trazem nome, e-mail, telefone, documento e data de nascimento. Trate o corpo do webhook como dado pessoal: receba apenas por HTTPS, restrinja o acesso aos logs e armazene e descarte conforme a LGPD.

Objeto payload.attributes

attributes é um mapa chave-valor aberto: as chaves abaixo são as mais comuns, mas a lista não é exaustiva.

CampoTipoDescrição
rfc
booleanBiometria facial ativa para o código.
rfc_provider
stringProvedor de biometria.
locale
string
freepass
booleanCortesia.
seat
stringAssento, quando o setor é numerado.
bundle_type
stringTipo do combo, quando o código vem de um.
attendee_external_ref
stringReferência do participante no sistema de origem do pedido. Opcional.
original_price_cents
intPreço original em centavos. Costuma coincidir com ticket.price.

Objeto payload.validation_metadata (apenas em code_used)

CampoTipoDescrição
date
string (RFC3339)Momento em que o check-in foi registrado.
entrance_type
stringcheckin | checkout
session_id
string (UUID)Sessão do check-in. Corresponde a ticket.group.event_dates[].id. Vem vazio ("") em validação de produto.

Backfill ao criar uma assinatura

Ao criar uma assinatura, a plataforma pode reenviar eventos passados dentro do escopo configurado. Prepare seu receptor para lidar com um volume inicial maior do que o regime normal.

Segurança e validação de assinatura

Toda requisição inclui o header X-Signature no formato sha256=<hash>. O hash é um HMAC-SHA256 do corpo bruto da requisição usando o secret da assinatura. Sempre valide a assinatura antes de processar o evento.

O secret é gerado no momento da criação da assinatura e entregue pela Ingresse durante o onboarding.

Além do X-Signature, cada requisição chega com Content-Type: application/json e User-Agent: awa-access-webhook/1.0.

Use o corpo bruto

Calcule o HMAC sobre os bytes exatos do corpo recebido, antes de qualquer parsing JSON. Os caracteres <, > e & chegam escapados como <, > e & — por exemplo dentro de ticket.description. Decodificar e reserializar o JSON desfaz esse escape e invalida a assinatura, mesmo com o conteúdo idêntico.

Requisitos da URL receptora

A URL cadastrada precisa usar HTTPS e ser acessível publicamente. URLs com http, localhost, 127.0.0.1, 0.0.0.0 ou endereços IP de rede privada ou loopback são rejeitadas no cadastro da assinatura.
JavaScript
const crypto = require('crypto')

function verificarAssinatura(segredo, corpo, cabecalho) {
  const esperado = Buffer.from('sha256=' + crypto.createHmac('sha256', segredo).update(corpo).digest('hex'))
  const recebido = Buffer.from(cabecalho ?? '')
  // timingSafeEqual lança se os buffers tiverem tamanhos diferentes — comparar antes.
  if (esperado.length !== recebido.length) return false
  return crypto.timingSafeEqual(esperado, recebido)
}

// Com Express: use express.raw para receber o corpo bruto
app.post('/webhook', express.raw({ type: '*/*' }), (req, res) => {
  if (!verificarAssinatura(process.env.SEGREDO, req.body, req.headers['x-signature'] ?? ''))
    return res.status(401).send()
  // processar evento de forma assíncrona...
  res.status(200).send()
})

Idempotência

A mesma notificação pode ser entregue mais de uma vez (por exemplo, após uma reentrega). Use o campo id como chave de idempotência para evitar processamento duplicado. O id do envelope é estável entre reentregas: a mesma notificação sempre chega com o mesmo id.

Python
# Exemplo com Redis (Python)
def processar(evento):
    chave = f"webhook:{evento['id']}"
    if redis.set(chave, 1, ex=86400, nx=True) is None:
        return  # já processado
    # processar...

Reentregas e timeouts

Responda com status 2xx o mais rápido possível (idealmente em poucos segundos) e processe o evento de forma assíncrona. O tempo de resposta considerado é de aproximadamente 25 segundos; acima disso a entrega é tratada como timeout. Respostas 3xx contam como sucesso — prefira responder 2xx diretamente.

Respostas 5xx ou timeout são reentregues automaticamente: a entrega é tentada até 3 vezes no total (a tentativa original mais 2 reentregas), com intervalo fixo de aproximadamente 2 minutos. Respostas 4xx (incluindo 429) são consideradas definitivas e não são reentregues. Após esgotadas as tentativas, a entrega é encaminhada para reprocessamento manual pela Ingresse.

Boas práticas

Confirme o recebimento rapidamente, enfileire o trabalho pesado e seja tolerante a entregas fora de ordem e a reentregas duplicadas.

Filtros

É possível restringir quais eventos sua URL recebe usando o objeto filters na criação da assinatura. Apenas eventos cujo payload corresponda aos filtros são entregues.

FiltroTipoDescrição
type
stringindividual | passport
status
stringStatus do ingresso (ex: cancelled).
event_id
string (UUID)Restringe a um evento. Use o ID interno (payload.event_id).
ticket_id
string (UUID)Restringe a um tipo de ingresso. Use o ID interno (payload.ticket_id).
can_transfer
booleanSomente transferíveis/não transferíveis.
attributes.rfc
booleanBiometria facial ativa.
attributes.rfc_provider
stringProvedor de biometria.

Filtros usam os IDs internos

Os filtros são comparados contra o payload entregue. Como payload.event_id e payload.ticket_id são IDs internos, filtrar pelo UUID externo não casa com nada e a assinatura para de receber, sem erro. O escopo event_id da assinatura é diferente: esse usa o UUID externo.

Somente ingressos individuais com biometria ativa

JSON
{
  "filters": {
    "type": "individual",
    "attributes": {
      "rfc": true
    }
  }
}

Somente ingressos com status cancelado

JSON
{
  "filters": {
    "status": "cancelled"
  }
}

Tipo de ingresso específico, apenas transferíveis

JSON
{
  "filters": {
    "ticket_id": "950e8400-e29b-41d4-a716-446655440000",
    "can_transfer": true
  }
}

Todos os ingressos de um evento com biometria obrigatória

JSON
{
  "filters": {
    "event_id": "850e8400-e29b-41d4-a716-446655440000",
    "attributes": {
      "rfc": true
    }
  }
}