# Documentação da API do IngressoJá (v1.1.0) Fonte: https://ingressoja.com/docs · Contrato OpenAPI: https://ingressoja.com/docs/openapi.json · Suporte: contato@ingressoja.com O IngressoJá tem duas APIs, no mesmo endereço base `https://api.ingressoja.com/v1`: 1. **API de eventos e ingressos**: para quem vende ingressos no IngressoJá e quer ligar catraca, CRM, ERP ou relatórios. Eventos, setores, pedidos, ingressos, check-in e webhooks. Autenticação com token `Authorization: Bearer ij_...`. 2. **API de pagamentos (PIX)**: para o parceiro que já tem o próprio sistema (loja, plataforma, app) e usa o IngressoJá como gateway de pagamento. Gera PIX, consulta e recebe o aviso de pagamento. Autenticação com `x-public-key` e `x-secret-key`. Regras gerais: JSON em UTF-8; datas em ISO 8601 (UTC), fuso dos eventos `America/Sao_Paulo`; só HTTPS. Os tokens e chaves são do servidor: nunca coloque em site, app ou código que roda no navegador. --- ## 1. API de eventos e ingressos ### Autenticação Crie o token no painel em **Integrações › API** (https://app.ingressoja.com/dashboard/integrations/api). Escolha as permissões: `read` (eventos, pedidos e ingressos) e/ou `checkin` (validar ingresso na entrada). O token aparece uma única vez; se perder, revogue e crie outro. Envie no cabeçalho `Authorization: Bearer ij_...` (ou `X-Api-Token`). Cada token só enxerga os eventos da conta em que foi criado. ```bash curl "https://api.ingressoja.com/v1/events" -H "Authorization: Bearer SEU_TOKEN" ``` ### Formato e paginação - O conteúdo vem sempre em `data`. - Valores em **centavos** (`8000` = R$ 80,00). - O `id` de um evento é o código do link dele (ingressoja.com/eventos/**89646811269**); o id interno também é aceito. - Listas: `?limit=` (1 a 200, padrão 50). Quando `pagination.has_more` for `true`, peça a próxima página com `?cursor=` + `pagination.next_cursor`. ### Erros e limites Erros vêm como `{ "error": { "code": "...", "message": "..." } }`. Limite: 120 pedidos por minuto por token. | Status | code | Quando | | --- | --- | --- | | 400 | invalid_status / invalid_date / invalid_session | Parâmetro com valor inválido. | | 401 | missing_token / invalid_token | Token ausente, errado ou revogado. | | 403 | insufficient_scope | O token não tem a permissão da rota (leitura ou check-in). | | 404 | event_not_found / order_not_found / ticket_not_found | Não existe, ou não é da sua conta. | | 429 | rate_limited | Mais de 120 pedidos por minuto com o mesmo token. | ### Objetos #### Evento Um evento da sua conta. Em "Buscar evento" vem também a lista sectors. | Campo | Tipo | Descrição | | --- | --- | --- | | `id` | string | Código público do evento, o número do link (ingressoja.com/eventos/89646811269). É o {id} das rotas. | | `internal_id` | string (uuid) | Id interno. Também é aceito no lugar de id nas rotas. | | `name` | string | Nome do evento. | | `subtitle` | string \| null | Subtítulo (turnê, edição, atração de abertura). | | `category` | string \| null | Categoria mostrada no site (Show, Festa, Teatro…). | | `kind` | "single" \| "festival" | single = uma data; festival = vários dias, listados em sessions. | | `status` | "draft" \| "upcoming" \| "published" \| "cancelled" | draft = rascunho; upcoming = divulgado, vendas ainda não abertas; published = à venda; cancelled = cancelado. | | `sales_paused` | boolean | true quando o organizador pausou as vendas. | | `starts_at` | string (ISO 8601) \| null | Início, em UTC. null quando a data ainda não foi definida. | | `ends_at` | string \| null | Término previsto, em UTC. | | `gates_open_at` | string \| null | Abertura dos portões, em UTC. | | `sales_start_at` | string \| null | Quando as vendas abrem (eventos upcoming). | | `timezone` | string | Sempre "America/Sao_Paulo": converta as datas para mostrar no horário local. | | `age_rating` | string \| null | Classificação indicativa ("L", "14", "16", "18"). | | `image_url` | string \| null | Banner do evento. | | `url` | string | Página de venda do evento. | | `venue` | object | Local: name, city, state e address (street, number, district, zip_code), ou address null quando o endereço não é público. | | `sessions` | array | Dias do festival: id, title, starts_at, gates_open_at. Vazio em eventos de uma data. | | `sectors` | array | Só em "Buscar evento": os setores e tipos de ingresso (veja Setor). | | `created_at / updated_at` | string | Criação e última alteração, em UTC. | #### Setor Um tipo de ingresso à venda (Pista Inteira, Pista Meia, Camarote…). Vem em sectors de "Buscar evento". | Campo | Tipo | Descrição | | --- | --- | --- | | `id` | string (uuid) | Id do setor. Use em sector_id para filtrar ingressos. | | `name` | string | Nome do ingresso. Com ingressos avançados é o tipo ("Inteira", "Meia-entrada"). | | `group` | string \| null | Área a que o tipo pertence ("Pista"). null no modo simples. | | `kind` | "full" \| "half" \| "other" | full = inteira; half = meia-entrada (pede comprovante na entrada); other = cortesia, promocional etc. | | `session_id` | string \| null | Festival: o dia (id em sessions) em que o ingresso vale. | | `passport` | boolean | Festival: true quando vale para todos os dias (entra uma vez por dia). | | `price` | integer | Preço atual em centavos (8000 = R$ 80,00), sem a taxa de serviço. | | `capacity` | integer | Total de lugares do setor. | | `sold` | integer | Ingressos vendidos (pedidos pagos). | | `available` | integer | Lugares ainda livres: capacidade menos vendidos e reservados (pedidos aguardando pagamento). | | `active` | boolean | false quando o setor foi desativado e não aparece na venda. | #### Pedido Uma compra. Em "Buscar pedido" e nos webhooks vem também a lista tickets. | Campo | Tipo | Descrição | | --- | --- | --- | | `id` | string (uuid) | Id do pedido. | | `number` | string | Número curto, o que o cliente vê no e-mail (8 primeiros caracteres do id). | | `event_id` | string | Código do evento. | | `status` | "pending" \| "paid" \| "expired" \| "cancelled" \| "refunded" | pending = aguardando pagamento; paid = pago, ingressos emitidos; expired = o tempo para pagar acabou; cancelled = cancelado; refunded = estornado ou contestado. | | `channel` | "online" \| "pdv" | online = site ou app; pdv = bilheteria física. | | `payment_method` | string \| null | Online: pix, card ou wallet (Apple Pay / Google Pay). Bilheteria: cash, debit, credit, pix ou courtesy. | | `installments` | integer \| null | Parcelas no cartão. | | `created_at / paid_at / cancelled_at` | string \| null | Datas em UTC. | | `buyer` | object | Comprador: name, email, phone. Na bilheteria podem vir null. | | `items` | array | sector_id, sector (nome completo), batch (lote), quantity, unit_price (centavos) e tickets_per_unit (mesa de 4 = 4). | | `tickets_count` | integer | Quantos ingressos o pedido gerou. | | `amounts` | object | Em centavos: tickets (valor dos ingressos), discount (cupom), service_fee, service_fee_payer ("customer" ou "organizer"), charged (o que o cliente pagou) e organizer_net (o que fica para você). | | `coupon` | string \| null | Código do cupom usado. | | `promoter_id` | string \| null | Promoter do link pelo qual a venda veio. | | `utm` | object \| null | Origem da venda: source, medium, campaign, content, term (e o clique do anúncio, quando houver). | #### Ingresso Um ingresso emitido (um participante). | Campo | Tipo | Descrição | | --- | --- | --- | | `id` | string (uuid) | Id do ingresso. | | `code` | string | Código do ingresso: é o conteúdo do QR code. Use no check-in. | | `event_id / order_id` | string | Evento e pedido do ingresso. | | `status` | "valid" \| "used" \| "cancelled" | valid = ainda não entrou; used = entrada feita; cancelled = cancelado (estorno, cancelamento). | | `sector_id / sector` | string | Setor e o nome completo dele ("Pista · Inteira"). | | `half_price` | boolean | true para meia-entrada: confira o documento na entrada. | | `spot` | string \| null | Lugar marcado (mesa, cadeira), quando o setor tem. | | `holder_name` | string \| null | Nome no ingresso (ou do comprador). | | `buyer_email` | string \| null | E-mail de quem comprou. | | `used_at / used_by` | string \| null | Quando e por quem a entrada foi feita (nome do leitor da portaria ou "API: nome do token"). | | `created_at` | string | Emissão, em UTC. | ### Endpoints ### Listar eventos `GET https://api.ingressoja.com/v1/events` Eventos da sua conta, do mais próximo para o mais distante. Para os setores de cada evento, use "Buscar evento". Permissão do token: `read`. Lista paginada: `?limit=` e `?cursor=` (veja Paginação). | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | --- | | `status` | query | string | não | Situação do evento. Valores: `published`, `upcoming`, `draft`, `cancelled`. | | `from` | query | string | não | Só eventos que começam a partir desta data (AAAA-MM-DD, no horário de Brasília, ou ISO 8601). | | `limit` | query | integer | não | Itens por página, de 1 a 200. Padrão: 50. | | `cursor` | query | string | não | O pagination.next_cursor da página anterior. | **Exemplo (cURL)** ```bash curl "https://api.ingressoja.com/v1/events?status=published&from=2026-11-01" \ -H "Authorization: Bearer SEU_TOKEN" ``` **Resposta 200** ```json { "data": [ { "id": "89646811269", "internal_id": "6a1f2c9e-0b5d-4e2a-9c41-3f8e7d2b1a90", "name": "Danilo & Davi", "subtitle": "Turnê Coração Sertanejo", "category": "Show", "kind": "single", "status": "published", "sales_paused": false, "starts_at": "2026-11-07T01:00:00.000Z", "ends_at": "2026-11-07T06:00:00.000Z", "gates_open_at": "2026-11-06T23:00:00.000Z", "sales_start_at": null, "timezone": "America/Sao_Paulo", "age_rating": "18", "image_url": "https://ingressoja.com/s3/ingressoja/public/eventos/danilo-e-davi.webp", "url": "https://ingressoja.com/eventos/89646811269", "venue": { "name": "Villa Country", "city": "São Paulo", "state": "SP", "address": { "street": "Av. Francisco Matarazzo", "number": "774", "district": "Barra Funda", "zip_code": "05001100" } }, "sessions": [], "created_at": "2026-10-17T23:51:10.000Z", "updated_at": "2026-10-18T14:02:44.000Z" } ], "pagination": { "limit": 50, "has_more": true, "next_cursor": "4a088077-e3c4-4fff-a87a-2fa706b36664" } } ``` ### Buscar evento `GET https://api.ingressoja.com/v1/events/{id}` Um evento com os setores, preços (em centavos), vendidos e disponíveis. Festivais trazem os dias em sessions. Permissão do token: `read`. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | --- | | `id` | caminho | string | sim | Código do evento (o número do link: ingressoja.com/eventos/89646811269). | **Exemplo (cURL)** ```bash curl "https://api.ingressoja.com/v1/events/89646811269" \ -H "Authorization: Bearer SEU_TOKEN" ``` **Resposta 200** ```json { "data": { "id": "89646811269", "internal_id": "6a1f2c9e-0b5d-4e2a-9c41-3f8e7d2b1a90", "name": "Danilo & Davi", "subtitle": "Turnê Coração Sertanejo", "category": "Show", "kind": "single", "status": "published", "sales_paused": false, "starts_at": "2026-11-07T01:00:00.000Z", "ends_at": "2026-11-07T06:00:00.000Z", "gates_open_at": "2026-11-06T23:00:00.000Z", "sales_start_at": null, "timezone": "America/Sao_Paulo", "age_rating": "18", "image_url": "https://ingressoja.com/s3/ingressoja/public/eventos/danilo-e-davi.webp", "url": "https://ingressoja.com/eventos/89646811269", "venue": { "name": "Villa Country", "city": "São Paulo", "state": "SP", "address": { "street": "Av. Francisco Matarazzo", "number": "774", "district": "Barra Funda", "zip_code": "05001100" } }, "sessions": [], "created_at": "2026-10-17T23:51:10.000Z", "updated_at": "2026-10-18T14:02:44.000Z", "sectors": [ { "id": "b2f0c0aa-1c2d-4e5f-8a9b-0c1d2e3f4a5b", "name": "Inteira", "group": "Pista", "kind": "full", "session_id": null, "passport": false, "price": 8000, "capacity": 2000, "sold": 812, "available": 1170, "active": true }, { "id": "c3a1d1bb-2d3e-4f60-9bac-1d2e3f4a5b6c", "name": "Meia-entrada", "group": "Pista", "kind": "half", "session_id": null, "passport": false, "price": 4000, "capacity": 500, "sold": 203, "available": 290, "active": true }, { "id": "d4b2e2cc-3e4f-4071-8cbd-2e3f4a5b6c7d", "name": "Camarote Villa", "group": null, "kind": "full", "session_id": null, "passport": false, "price": 22000, "capacity": 300, "sold": 214, "available": 80, "active": true } ] } } ``` ### Listar pedidos `GET https://api.ingressoja.com/v1/events/{id}/orders` Pedidos do evento, do mais recente para o mais antigo, com comprador, itens, valores e origem (UTM). Permissão do token: `read`. Lista paginada: `?limit=` e `?cursor=` (veja Paginação). | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | --- | | `id` | caminho | string | sim | Código do evento (o número do link: ingressoja.com/eventos/89646811269). | | `status` | query | string | não | Situação do pedido. Valores: `paid`, `pending`, `cancelled`, `refunded`, `expired`. | | `email` | query | string | não | Só pedidos deste e-mail de comprador. | | `from` | query | string | não | Pedidos feitos a partir desta data. | | `to` | query | string | não | Pedidos feitos até esta data. | | `limit` | query | integer | não | Itens por página, de 1 a 200. Padrão: 50. | | `cursor` | query | string | não | O pagination.next_cursor da página anterior. | **Exemplo (cURL)** ```bash curl "https://api.ingressoja.com/v1/events/89646811269/orders?status=paid&limit=100" \ -H "Authorization: Bearer SEU_TOKEN" ``` **Resposta 200** ```json { "data": [ { "id": "7d2c1b0a-9e8f-4a7b-b6c5-d4e3f2a1b0c9", "number": "7D2C1B0A", "event_id": "89646811269", "status": "paid", "channel": "online", "payment_method": "pix", "installments": null, "created_at": "2026-10-18T15:18:40.000Z", "paid_at": "2026-10-18T15:20:03.000Z", "cancelled_at": null, "buyer": { "name": "Maria Souza", "email": "maria.souza@gmail.com", "phone": "(11) 98765-4321" }, "items": [ { "sector_id": "b2f0c0aa-1c2d-4e5f-8a9b-0c1d2e3f4a5b", "sector": "Pista · Inteira", "batch": "2º lote", "quantity": 2, "unit_price": 8000, "tickets_per_unit": 1 } ], "tickets_count": 2, "amounts": { "tickets": 16000, "discount": 0, "service_fee": 1600, "service_fee_payer": "customer", "charged": 17600, "organizer_net": 16000 }, "coupon": null, "promoter_id": null, "utm": { "source": "instagram", "medium": "bio", "campaign": "lancamento" } } ], "pagination": { "limit": 50, "has_more": true, "next_cursor": "4a088077-e3c4-4fff-a87a-2fa706b36664" } } ``` ### Buscar pedido `GET https://api.ingressoja.com/v1/events/{id}/orders/{orderId}` Um pedido com todos os ingressos emitidos. Permissão do token: `read`. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | --- | | `id` | caminho | string | sim | Código do evento (o número do link: ingressoja.com/eventos/89646811269). | | `orderId` | caminho | string | sim | Id do pedido. | **Exemplo (cURL)** ```bash curl "https://api.ingressoja.com/v1/events/89646811269/orders/7d2c1b0a-9e8f-4a7b-b6c5-d4e3f2a1b0c9" \ -H "Authorization: Bearer SEU_TOKEN" ``` **Resposta 200** ```json { "data": { "id": "7d2c1b0a-9e8f-4a7b-b6c5-d4e3f2a1b0c9", "number": "7D2C1B0A", "event_id": "89646811269", "status": "paid", "channel": "online", "payment_method": "pix", "installments": null, "created_at": "2026-10-18T15:18:40.000Z", "paid_at": "2026-10-18T15:20:03.000Z", "cancelled_at": null, "buyer": { "name": "Maria Souza", "email": "maria.souza@gmail.com", "phone": "(11) 98765-4321" }, "items": [ { "sector_id": "b2f0c0aa-1c2d-4e5f-8a9b-0c1d2e3f4a5b", "sector": "Pista · Inteira", "batch": "2º lote", "quantity": 2, "unit_price": 8000, "tickets_per_unit": 1 } ], "tickets_count": 2, "amounts": { "tickets": 16000, "discount": 0, "service_fee": 1600, "service_fee_payer": "customer", "charged": 17600, "organizer_net": 16000 }, "coupon": null, "promoter_id": null, "utm": { "source": "instagram", "medium": "bio", "campaign": "lancamento" }, "tickets": [ { "id": "0f3e2d1c-4b5a-6978-8a9b-0c1d2e3f4a5b", "code": "IJ7K2M9QX4", "event_id": "89646811269", "order_id": "7d2c1b0a-9e8f-4a7b-b6c5-d4e3f2a1b0c9", "status": "valid", "sector_id": "b2f0c0aa-1c2d-4e5f-8a9b-0c1d2e3f4a5b", "sector": "Pista · Inteira", "half_price": false, "spot": null, "holder_name": "Maria Souza", "buyer_email": "maria.souza@gmail.com", "used_at": null, "used_by": null, "created_at": "2026-10-18T15:20:04.000Z" }, { "id": "1a4f3e2d-5c6b-4a89-9bac-1d2e3f4a5b6c", "code": "IJQ8R3T6W1", "event_id": "89646811269", "order_id": "7d2c1b0a-9e8f-4a7b-b6c5-d4e3f2a1b0c9", "status": "valid", "sector_id": "b2f0c0aa-1c2d-4e5f-8a9b-0c1d2e3f4a5b", "sector": "Pista · Inteira", "half_price": false, "spot": null, "holder_name": "João Souza", "buyer_email": "maria.souza@gmail.com", "used_at": null, "used_by": null, "created_at": "2026-10-18T15:20:04.000Z" } ] } } ``` ### Listar ingressos `GET https://api.ingressoja.com/v1/events/{id}/tickets` Ingressos emitidos (a lista de participantes) do evento, na ordem de emissão. Permissão do token: `read`. Lista paginada: `?limit=` e `?cursor=` (veja Paginação). | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | --- | | `id` | caminho | string | sim | Código do evento (o número do link: ingressoja.com/eventos/89646811269). | | `status` | query | string | não | Situação do ingresso. Valores: `valid`, `used`, `cancelled`. | | `email` | query | string | não | E-mail do comprador. | | `sector_id` | query | string | não | Só ingressos deste setor. | | `order_id` | query | string | não | Só ingressos deste pedido. | | `limit` | query | integer | não | Itens por página, de 1 a 200. Padrão: 50. | | `cursor` | query | string | não | O pagination.next_cursor da página anterior. | **Exemplo (cURL)** ```bash curl "https://api.ingressoja.com/v1/events/89646811269/tickets?status=valid&limit=200" \ -H "Authorization: Bearer SEU_TOKEN" ``` **Resposta 200** ```json { "data": [ { "id": "0f3e2d1c-4b5a-6978-8a9b-0c1d2e3f4a5b", "code": "IJ7K2M9QX4", "event_id": "89646811269", "order_id": "7d2c1b0a-9e8f-4a7b-b6c5-d4e3f2a1b0c9", "status": "valid", "sector_id": "b2f0c0aa-1c2d-4e5f-8a9b-0c1d2e3f4a5b", "sector": "Pista · Inteira", "half_price": false, "spot": null, "holder_name": "Maria Souza", "buyer_email": "maria.souza@gmail.com", "used_at": null, "used_by": null, "created_at": "2026-10-18T15:20:04.000Z" } ], "pagination": { "limit": 50, "has_more": true, "next_cursor": "4a088077-e3c4-4fff-a87a-2fa706b36664" } } ``` ### Buscar ingresso `GET https://api.ingressoja.com/v1/events/{id}/tickets/{code}` Um ingresso pelo código (o mesmo conteúdo do QR code). Permissão do token: `read`. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | --- | | `id` | caminho | string | sim | Código do evento (o número do link: ingressoja.com/eventos/89646811269). | | `code` | caminho | string | sim | Código do ingresso. | **Exemplo (cURL)** ```bash curl "https://api.ingressoja.com/v1/events/89646811269/tickets/IJ7K2M9QX4" \ -H "Authorization: Bearer SEU_TOKEN" ``` **Resposta 200** ```json { "data": { "id": "0f3e2d1c-4b5a-6978-8a9b-0c1d2e3f4a5b", "code": "IJ7K2M9QX4", "event_id": "89646811269", "order_id": "7d2c1b0a-9e8f-4a7b-b6c5-d4e3f2a1b0c9", "status": "valid", "sector_id": "b2f0c0aa-1c2d-4e5f-8a9b-0c1d2e3f4a5b", "sector": "Pista · Inteira", "half_price": false, "spot": null, "holder_name": "Maria Souza", "buyer_email": "maria.souza@gmail.com", "used_at": null, "used_by": null, "created_at": "2026-10-18T15:20:04.000Z" } } ``` ### Fazer check-in `POST https://api.ingressoja.com/v1/events/{id}/tickets/{code}/check-in` Valida o ingresso na entrada (catraca, totem, app próprio). Segue a mesma regra da portaria do IngressoJá: libera uma única vez, e a leitura repetida volta "used". Sempre responde 200 quando o ingresso foi lido; o resultado está em data.result. Permissão do token: `checkin`. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | --- | | `id` | caminho | string | sim | Código do evento (o número do link: ingressoja.com/eventos/89646811269). | | `code` | caminho | string | sim | Código lido no QR code. | | `session_id` | corpo (JSON) | string | não | Festival: o dia da entrada (id em sessions). O passaporte entra uma vez por dia. | **Exemplo (cURL)** ```bash curl -X POST "https://api.ingressoja.com/v1/events/89646811269/tickets/IJ7K2M9QX4/check-in" \ -H "Authorization: Bearer SEU_TOKEN" \ -H "Content-Type: application/json" \ -d '{}' ``` **Resposta 200** ```json { "data": { "result": "ok", "message": "Entrada liberada.", "code": "IJ7K2M9QX4", "sector": "Pista · Inteira", "half_price": false, "holder_name": "Maria Souza", "used_at": null, "used_by": null } } ``` - result "ok": entrada liberada. - result "used": já entrou. used_at e used_by dizem quando e onde (ex.: "Este ingresso já foi utilizado."). - result "invalid": não libere. message explica: "Ingresso não encontrado.", "Este ingresso é de outro evento.", "Ingresso cancelado." ou, em festival, "Este ingresso é de outro dia do festival (…)". - Dois leitores com o mesmo ingresso ao mesmo tempo: só um recebe "ok". #### Resultados do check-in | data.result | Significa | O que fazer | | --- | --- | --- | | `ok` | Entrada liberada. | Abra a catraca. | | `used` | O ingresso já entrou (used_at, used_by). | Não libere; mostre quando e onde entrou. | | `invalid` | Não encontrado, de outro evento ou dia, ou cancelado. | Não libere; mostre data.message. | ### Webhooks de eventos Cadastre o endereço https do seu sistema em **Integrações › Webhooks** (https://app.ingressoja.com/dashboard/integrations/webhook), escolha os avisos e, se quiser, só alguns eventos. Cada aviso chega como `POST` com JSON `{ id, type, created_at, data }`. Responda com qualquer status 2xx em até 10 segundos; se o seu sistema estiver fora, tentamos de novo depois de 1, 5, 30, 120, 360 minutos. | type | Quando | Em data | | --- | --- | --- | | `order.paid` | O pagamento foi aprovado e os ingressos foram emitidos (site, app ou bilheteria). | event + order (com tickets) | | `order.refunded` | O pagamento foi devolvido ou contestado; os ingressos válidos foram cancelados. | event + order (com tickets) | | `order.cancelled` | Uma venda paga foi cancelada (por exemplo, na bilheteria). | event + order (com tickets) | | `ticket.checked_in` | Entrada liberada na portaria, no painel ou pela API. | event + ticket + check_in { at, by, session_id } | | `event.updated` | O evento foi salvo, ou as vendas foram pausadas ou retomadas. | event (com sectors) | | `ping` | Botão "Enviar teste" no painel. | { message } | Cabeçalhos: `X-IngressoJa-Event` (o tipo), `X-IngressoJa-Delivery` (id único da entrega: use para ignorar repetidos) e `X-IngressoJa-Signature: t=,v1=`. **Exemplo: order.paid** ```json { "id": "5b9c1e2f-3a4d-4e6f-8a7b-9c0d1e2f3a4b", "type": "order.paid", "created_at": "2026-10-18T15:20:04.000Z", "data": { "event": { "id": "89646811269", "internal_id": "6a1f2c9e-0b5d-4e2a-9c41-3f8e7d2b1a90", "name": "Danilo & Davi", "subtitle": "Turnê Coração Sertanejo", "category": "Show", "kind": "single", "status": "published", "sales_paused": false, "starts_at": "2026-11-07T01:00:00.000Z", "ends_at": "2026-11-07T06:00:00.000Z", "gates_open_at": "2026-11-06T23:00:00.000Z", "sales_start_at": null, "timezone": "America/Sao_Paulo", "age_rating": "18", "image_url": "https://ingressoja.com/s3/ingressoja/public/eventos/danilo-e-davi.webp", "url": "https://ingressoja.com/eventos/89646811269", "venue": { "name": "Villa Country", "city": "São Paulo", "state": "SP", "address": { "street": "Av. Francisco Matarazzo", "number": "774", "district": "Barra Funda", "zip_code": "05001100" } }, "sessions": [], "created_at": "2026-10-17T23:51:10.000Z", "updated_at": "2026-10-18T14:02:44.000Z" }, "order": { "id": "7d2c1b0a-9e8f-4a7b-b6c5-d4e3f2a1b0c9", "number": "7D2C1B0A", "event_id": "89646811269", "status": "paid", "channel": "online", "payment_method": "pix", "installments": null, "created_at": "2026-10-18T15:18:40.000Z", "paid_at": "2026-10-18T15:20:03.000Z", "cancelled_at": null, "buyer": { "name": "Maria Souza", "email": "maria.souza@gmail.com", "phone": "(11) 98765-4321" }, "items": [ { "sector_id": "b2f0c0aa-1c2d-4e5f-8a9b-0c1d2e3f4a5b", "sector": "Pista · Inteira", "batch": "2º lote", "quantity": 2, "unit_price": 8000, "tickets_per_unit": 1 } ], "tickets_count": 2, "amounts": { "tickets": 16000, "discount": 0, "service_fee": 1600, "service_fee_payer": "customer", "charged": 17600, "organizer_net": 16000 }, "coupon": null, "promoter_id": null, "utm": { "source": "instagram", "medium": "bio", "campaign": "lancamento" }, "tickets": [ { "id": "0f3e2d1c-4b5a-6978-8a9b-0c1d2e3f4a5b", "code": "IJ7K2M9QX4", "event_id": "89646811269", "order_id": "7d2c1b0a-9e8f-4a7b-b6c5-d4e3f2a1b0c9", "status": "valid", "sector_id": "b2f0c0aa-1c2d-4e5f-8a9b-0c1d2e3f4a5b", "sector": "Pista · Inteira", "half_price": false, "spot": null, "holder_name": "Maria Souza", "buyer_email": "maria.souza@gmail.com", "used_at": null, "used_by": null, "created_at": "2026-10-18T15:20:04.000Z" } ] } } } ``` **Exemplo: ticket.checked_in** ```json { "id": "8e1d2c3b-4a5f-4e6d-9c8b-7a6f5e4d3c2b", "type": "ticket.checked_in", "created_at": "2026-11-06T23:41:12.000Z", "data": { "event": { "id": "89646811269", "internal_id": "6a1f2c9e-0b5d-4e2a-9c41-3f8e7d2b1a90", "name": "Danilo & Davi", "subtitle": "Turnê Coração Sertanejo", "category": "Show", "kind": "single", "status": "published", "sales_paused": false, "starts_at": "2026-11-07T01:00:00.000Z", "ends_at": "2026-11-07T06:00:00.000Z", "gates_open_at": "2026-11-06T23:00:00.000Z", "sales_start_at": null, "timezone": "America/Sao_Paulo", "age_rating": "18", "image_url": "https://ingressoja.com/s3/ingressoja/public/eventos/danilo-e-davi.webp", "url": "https://ingressoja.com/eventos/89646811269", "venue": { "name": "Villa Country", "city": "São Paulo", "state": "SP", "address": { "street": "Av. Francisco Matarazzo", "number": "774", "district": "Barra Funda", "zip_code": "05001100" } }, "sessions": [], "created_at": "2026-10-17T23:51:10.000Z", "updated_at": "2026-10-18T14:02:44.000Z" }, "ticket": { "id": "0f3e2d1c-4b5a-6978-8a9b-0c1d2e3f4a5b", "code": "IJ7K2M9QX4", "event_id": "89646811269", "order_id": "7d2c1b0a-9e8f-4a7b-b6c5-d4e3f2a1b0c9", "status": "used", "sector_id": "b2f0c0aa-1c2d-4e5f-8a9b-0c1d2e3f4a5b", "sector": "Pista · Inteira", "half_price": false, "spot": null, "holder_name": "Maria Souza", "buyer_email": "maria.souza@gmail.com", "used_at": "2026-11-06T23:41:12.000Z", "used_by": "Portão A", "created_at": "2026-10-18T15:20:04.000Z" }, "check_in": { "at": "2026-11-06T23:41:12.000Z", "by": "Portão A", "session_id": null } } } ``` #### Conferir a assinatura Ao criar o webhook, o painel mostra um segredo (`whsec_...`). `v1` é o HMAC-SHA256, em hexadecimal, de `.` com esse segredo. Use o corpo exatamente como chegou e recuse avisos com `t` de mais de 5 minutos. ```js const crypto = require('crypto') // rawBody = o corpo exatamente como chegou (texto, antes do JSON.parse) function webhookValido(headerAssinatura, rawBody, segredo) { const partes = Object.fromEntries(headerAssinatura.split(',').map((p) => p.split('='))) const esperado = crypto.createHmac('sha256', segredo).update(partes.t + '.' + rawBody).digest('hex') const recente = Math.abs(Date.now() / 1000 - Number(partes.t)) < 300 // até 5 minutos return recente && crypto.timingSafeEqual(Buffer.from(partes.v1), Buffer.from(esperado)) } ``` ```python import hmac, hashlib, time def webhook_valido(header, corpo: bytes, segredo: str) -> bool: partes = dict(p.split("=", 1) for p in header.split(",")) esperado = hmac.new(segredo.encode(), partes["t"].encode() + b"." + corpo, hashlib.sha256).hexdigest() return hmac.compare_digest(esperado, partes["v1"]) and abs(time.time() - int(partes["t"])) < 300 ``` ```php ). pix.base64 é a mesma imagem sem o prefixo data:. - order.url é reservado (sempre null por enquanto): a tela de pagamento é a sua. **Exemplos em outras linguagens** ```js const response = await fetch('https://api.ingressoja.com/v1/gateway/pix/receive', { method: 'POST', headers: { 'x-public-key': process.env.INGRESSOJA_PUBLIC_KEY, 'x-secret-key': process.env.INGRESSOJA_SECRET_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ "identifier": "PED-2026-000187", "amount": 89.9, "client": { "name": "Ana Ribeiro", "email": "ana.ribeiro@gmail.com", "phone": "11987654321", "document": "529.982.247-25" }, "products": [ { "id": "plano-anual", "name": "Plano anual", "quantity": 1, "price": 89.9 } ], "dueDate": "2026-11-05T23:59:00-03:00", "callbackUrl": "https://loja.exemplo.com.br/webhooks/ingressoja", "metadata": { "pedido_loja": "A-77" } }), }) const result = await response.json() ``` ```python import os, requests response = requests.post( "https://api.ingressoja.com/v1/gateway/pix/receive", headers={ "x-public-key": os.environ["INGRESSOJA_PUBLIC_KEY"], "x-secret-key": os.environ["INGRESSOJA_SECRET_KEY"], "Content-Type": "application/json", }, json={ "identifier": "PED-2026-000187", "amount": 89.9, "client": { "name": "Ana Ribeiro", "email": "ana.ribeiro@gmail.com", "phone": "11987654321", "document": "529.982.247-25" }, "products": [ { "id": "plano-anual", "name": "Plano anual", "quantity": 1, "price": 89.9 } ], "dueDate": "2026-11-05T23:59:00-03:00", "callbackUrl": "https://loja.exemplo.com.br/webhooks/ingressoja", "metadata": { "pedido_loja": "A-77" } }, timeout=15, ) result = response.json() ``` ```php true, CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_HTTPHEADER => [ 'x-public-key: ' . getenv('INGRESSOJA_PUBLIC_KEY'), 'x-secret-key: ' . getenv('INGRESSOJA_SECRET_KEY'), 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'identifier' => 'PED-2026-000187', 'amount' => 89.9, 'client' => [ 'name' => 'Ana Ribeiro', 'email' => 'ana.ribeiro@gmail.com', 'phone' => '11987654321', 'document' => '529.982.247-25', ], 'products' => [ [ 'id' => 'plano-anual', 'name' => 'Plano anual', 'quantity' => 1, 'price' => 89.9, ], ], 'dueDate' => '2026-11-05T23:59:00-03:00', 'callbackUrl' => 'https://loja.exemplo.com.br/webhooks/ingressoja', 'metadata' => [ 'pedido_loja' => 'A-77', ], ]), ]); $result = json_decode(curl_exec($ch), true); ``` ### Consultar transação `GET https://api.ingressoja.com/v1/transactions` A situação de uma cobrança pelo id (transactionId) ou pelo identifier que você enviou. Só enxerga as cobranças da sua conta. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | --- | | `id` | query | string | não | O transactionId devolvido ao gerar o PIX. | | `identifier` | query | string | não | O identifier do seu pedido (o nome antigo externalId também é aceito). | **Exemplo (cURL)** ```bash curl "https://api.ingressoja.com/v1/transactions?identifier=PED-2026-000187" \ -H "x-public-key: SUA_CHAVE_PUBLICA" \ -H "x-secret-key: SUA_CHAVE_PRIVADA" ``` **Resposta 200** ```json { "success": true, "data": { "id": "PPf8d83fcf20", "identifier": "PED-2026-000187", "externalId": "PED-2026-000187", "currency": "BRL", "amount": 89.9, "chargeAmount": 89.9, "exchangeRate": 1, "producerExtraAmount": 0, "status": "COMPLETED", "statusDescription": null, "purchaseType": "ONCE", "paymentMethod": "PIX", "details": "Plano anual", "errorDescription": null, "webhookUrl": "https://loja.exemplo.com.br/webhooks/ingressoja", "availableAt": null, "createdAt": "2026-11-04T18:39:19.762Z", "expiresAt": "2026-11-06T02:59:00.000Z", "refundedAt": null, "payedAt": "2026-11-04T18:41:02.846Z", "boletoInformation": null, "pixInformation": { "qrCode": "00020101021226930014br.gov.bcb.pix2571pix-qr.mercadopago.com/instore/ol/v2/3Z9Kq8vM2xT4bY7nH1cR5wE0aF6dG520400005303986540589.905802BR5911IngressoJa6009SAO PAULO62070503***6304B1F7", "base64": "iVBORw0KGgoAAAANSUhEUgAAAQAAAAEAAQMAAABmvDolAAAABlBMVEX///8AAABVwtN+AAAB…", "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEAAQMAAABmvDolAAAABlBMVEX///8AAABVwtN+AAAB…" } }, "message": "Transação encontrada com sucesso" } ``` ### Cadastrar webhook `POST https://api.ingressoja.com/v1/webhooks` Um endereço que recebe os avisos de todas as cobranças da conta (sem precisar mandar callbackUrl em cada PIX). Também recebe os avisos das vendas de ingresso da conta. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | --- | | `name` | corpo (JSON) | string | sim | Nome para você reconhecer. | | `callbackUrl` | corpo (JSON) | string | sim | Endereço https. | | `events` | corpo (JSON) | array | sim | Avisos que quer receber. Valores: `TRANSACTION_PAID`, `TRANSACTION_CANCELED`, `TRANSACTION_REFUNDED`, `TRANSACTION_CHARGED_BACK`. | **Exemplo (cURL)** ```bash curl -X POST "https://api.ingressoja.com/v1/webhooks" \ -H "x-public-key: SUA_CHAVE_PUBLICA" \ -H "x-secret-key: SUA_CHAVE_PRIVADA" \ -H "Content-Type: application/json" \ -d '{ "name": "ERP da loja", "callbackUrl": "https://loja.exemplo.com.br/webhooks/ingressoja", "events": [ "TRANSACTION_PAID", "TRANSACTION_CANCELED", "TRANSACTION_REFUNDED", "TRANSACTION_CHARGED_BACK" ] }' ``` **Resposta 201** ```json { "id": "3c9a1f52-7d1e-4b0a-a8c2-5e6f7a8b9c0d", "name": "ERP da loja", "callbackUrl": "https://loja.exemplo.com.br/webhooks/ingressoja", "events": [ "TRANSACTION_PAID", "TRANSACTION_CANCELED", "TRANSACTION_REFUNDED", "TRANSACTION_CHARGED_BACK" ], "token": "wh_Q2n8Zk1VbT7mR4xY9pLc0aSd", "status": "active", "allProducts": true, "productIds": [], "createdAt": "2026-11-04T18:30:00.000Z" } ``` - Guarde o token: ele vem no campo token de cada aviso deste webhook. ### Listar webhooks `GET https://api.ingressoja.com/v1/webhooks` Os webhooks de pagamento cadastrados na sua conta. **Exemplo (cURL)** ```bash curl "https://api.ingressoja.com/v1/webhooks" \ -H "x-public-key: SUA_CHAVE_PUBLICA" \ -H "x-secret-key: SUA_CHAVE_PRIVADA" ``` **Resposta 200** ```json [ { "id": "3c9a1f52-7d1e-4b0a-a8c2-5e6f7a8b9c0d", "name": "ERP da loja", "callbackUrl": "https://loja.exemplo.com.br/webhooks/ingressoja", "events": [ "TRANSACTION_PAID", "TRANSACTION_CANCELED", "TRANSACTION_REFUNDED", "TRANSACTION_CHARGED_BACK" ], "token": "wh_Q2n8Zk1VbT7mR4xY9pLc0aSd", "status": "active", "allProducts": true, "productIds": [], "createdAt": "2026-11-04T18:30:00.000Z", "updatedAt": "2026-11-04T18:30:00.000Z" } ] ``` ### Remover webhook `DELETE https://api.ingressoja.com/v1/webhooks` Remove um webhook de pagamento da sua conta. | Parâmetro | Onde | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | --- | | `id` | query | string | sim | Id do webhook. | **Exemplo (cURL)** ```bash curl -X DELETE "https://api.ingressoja.com/v1/webhooks?id=3c9a1f52-7d1e-4b0a-a8c2-5e6f7a8b9c0d" \ -H "x-public-key: SUA_CHAVE_PUBLICA" \ -H "x-secret-key: SUA_CHAVE_PRIVADA" ``` **Resposta 200** ```json { "id": "3c9a1f52-7d1e-4b0a-a8c2-5e6f7a8b9c0d", "deleted": true } ``` ### Situações da cobrança | status | Significa | | --- | --- | | `PENDING` | PIX gerado, aguardando o pagamento. | | `COMPLETED` | Pago. Libere o pedido. | | `FAILED` | Venceu sem pagamento ou foi cancelado. Gere outro PIX (com outro identifier) se o cliente quiser pagar. | | `REFUNDED` | Pagamento devolvido ao pagador. | | `CHARGED_BACK` | Pagamento contestado. | ### Avisos de pagamento Mande `callbackUrl` ao gerar o PIX (um endereço por cobrança) e/ou cadastre um webhook da conta. O aviso chega como `POST` com JSON: | event | transaction.status | Quando | | --- | --- | --- | | `TRANSACTION_PAID` | `COMPLETED` | O PIX foi pago e confirmado pelo banco. | | `TRANSACTION_CANCELED` | `FAILED` | O PIX venceu sem pagamento ou foi cancelado. | | `TRANSACTION_REFUNDED` | `REFUNDED` | O pagamento foi devolvido. | | `TRANSACTION_CHARGED_BACK` | `CHARGED_BACK` | O pagamento foi contestado. | Cabeçalhos: `X-Event` (o evento) e `X-IngressoJa-Signature: sha256=`. Nos avisos de webhook cadastrado, o campo `token` traz o token daquele webhook (no `callbackUrl` ele vem vazio). Responda 2xx. Use `transaction.identifier` para achar o seu pedido e trate avisos repetidos: libere o pedido uma vez só. ```json { "event": "TRANSACTION_PAID", "token": "", "offerCode": "PED-2026-000187", "client": { "id": "NTI5Ljk4Mi", "name": "Ana Ribeiro", "email": "ana.ribeiro@gmail.com", "cpf": "529.982.247-25", "address": null }, "transaction": { "id": "PPf8d83fcf20", "identifier": "PED-2026-000187", "status": "COMPLETED", "paymentMethod": "PIX", "originalCurrency": "BRL", "originalAmount": "89.9", "currency": "BRL", "amount": "8990", "createdAt": "2026-11-04T18:39:19.762Z", "payedAt": "2026-11-04T18:41:02.846Z" }, "subscription": null, "orderItems": [ { "id": "UFBmOGQ4M2", "price": "8990", "product": { "id": "UGxhbm8gYW", "name": "Plano anual", "externalId": "PED-2026-000187" } } ], "trackProps": { "pedido_loja": "A-77" } } ``` #### Conferir a assinatura do aviso ```js const crypto = require('crypto') // rawBody = o corpo exatamente como chegou; chavePrivada = a sua x-secret-key function avisoValido(header, rawBody, chavePrivada) { const esperado = 'sha256=' + crypto.createHmac('sha256', chavePrivada).update(rawBody).digest('hex') return header?.length === esperado.length && crypto.timingSafeEqual(Buffer.from(header), Buffer.from(esperado)) } // Express: app.post('/webhooks/ingressoja', express.raw({ type: 'application/json' }), (req, res) => { // if (!avisoValido(req.get('X-IngressoJa-Signature'), req.body.toString('utf8'), process.env.INGRESSOJA_SECRET_KEY)) return res.sendStatus(401) // const aviso = JSON.parse(req.body) // if (aviso.event === 'TRANSACTION_PAID') liberarPedido(aviso.transaction.identifier) // res.sendStatus(200) // }) ``` ```python import hmac, hashlib def aviso_valido(header: str, corpo: bytes, chave_privada: str) -> bool: esperado = "sha256=" + hmac.new(chave_privada.encode(), corpo, hashlib.sha256).hexdigest() return hmac.compare_digest(esperado, header or "") ``` ```php