> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ephra.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Plataforma

> Cadastre um webhook, dispare um teste, investigue entregas com falha e configure supressão de eventos de alto volume.

Trinta e cinco operações para a conta em si: os **webhooks**, as entregas deles e as regras de supressão — a parte que mais aparece numa integração —, as **chaves de API**, os **membros do time**, o **cadastro da empresa** e a **subconta** que precisa estar aprovada antes de qualquer saque. As conexões com aplicativos externos, também deste domínio, têm página própria em [Integrações](/guias/integracoes).

<Warning>
  `/v1/webhooks` e a rota transacional legada `/v1/webhook`, no singular, **não são duas coleções**: são as mesmas linhas com dois contratos de resposta. Um webhook cadastrado aqui aparece na listagem de lá, com o mesmo `id`. O que muda é o envelope (`data` é uma lista aqui e um objeto `{ webhooks, pagination }` lá, com `offset`/`totalCount` no lugar de `page`/`total`) e o segredo: `GET /v1/webhook` devolve `signatureSecret` **inteiro, em texto puro**, e `GET /v1/webhooks` devolve `signatureSecretMasked`.

  Duas consequências práticas. Cadastrar o mesmo endpoint nas duas rotas não cria "uma assinatura em cada API" — cria **duas linhas para a mesma URL**, e cada evento é entregue uma vez por linha, ou seja, em dobro. E migrar da legada para esta não exige recadastro: os webhooks já estão aqui; basta trocar a URL da chamada.

  Use `/v1/webhooks` em integrações novas. A legada continua no ar e está documentada em [Cadastrar webhook](/api-reference/webhooks-criar).
</Warning>

## 1. Cadastre o endpoint

<Warning>
  A Ephra **testa a URL no momento do cadastro**, com um `POST` de verdade. Um endereço que não responde `2xx` recusa o cadastro:

  ```json theme={null}
  { "success": false, "message": "Não foi possivel testar a url do webhook" }
  ```

  Suba o endpoint antes de cadastrá-lo, e faça-o responder `200` a qualquer corpo.
</Warning>

```bash theme={null}
curl -s -X POST https://api.ephra.io/v1/webhooks \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://erp.minhaloja.com.br/integracoes/ephra",
    "events": ["transaction_paid", "transaction_refunded"],
    "name": "ERP da loja",
    "isActive": true,
    "productIds": ["cmty05wl0000apyg1bx1566ej"]
  }'
```

```json theme={null}
{
  "success": true,
  "data": {
    "id": 20,
    "name": "ERP da loja",
    "url": "https://erp.minhaloja.com.br/integracoes/ephra",
    "events": ["transaction_paid", "transaction_refunded"],
    "isActive": true,
    "productIds": ["cmty05wl0000apyg1bx1566ej"],
    "offerIds": [],
    "signatureSecretMasked": "••••••••146b",
    "mtlsCapable": false,
    "createdAt": "2026-09-12T06:28:41.855Z",
    "updatedAt": "2026-09-12T06:28:41.855Z"
  }
}
```

`productIds` vazio significa "todos os produtos". Preenchido, o webhook só dispara para os produtos listados — é assim que se separa um ERP que só cuida de uma linha de produtos.

<Note>
  O segredo de assinatura volta **mascarado** (`signatureSecretMasked`), e a máscara vale só para esta rota: `GET /v1/webhook` devolve o mesmo segredo inteiro, em texto puro, para qualquer token da empresa. Não trate o mascaramento como proteção. Some a isso o fato de que o segredo ainda não assina as entregas — leia [Verificação e segurança](/webhooks/seguranca) antes de construir qualquer validação em cima dele.
</Note>

## 2. Dispare um teste

```bash theme={null}
curl -s -X POST https://api.ephra.io/v1/webhooks/20/test \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"events": ["transaction_paid"]}'
```

```json theme={null}
{
  "success": true,
  "data": {
    "webhookId": 20,
    "url": "https://erp.minhaloja.com.br/integracoes/ephra",
    "results": [
      { "event": "transaction_paid", "success": true, "httpStatus": 200, "error": null }
    ]
  }
}
```

O `httpStatus` é o que **o seu servidor** respondeu. O payload que chega é um evento completo com `scope: "test"` e ids de teste:

```json theme={null}
{
  "id": "3ff1d2f6-386a-4627-8d45-570b418ab970",
  "type": "transaction",
  "event": "transaction_paid",
  "scope": "test",
  "transaction": {
    "id": "test_transaction_id",
    "amount": 49900,
    "netAmount": 44910,
    "status": "paid",
    "pix": {
      "endToEndId": "E00000000202506301200TEST00000001",
      "payerInfo": { "name": "Cliente Teste", "document": "00000000000" }
    },
    "paidAt": "2026-09-12T06:28:41.924Z",
    "refundPeriodDays": 7,
    "refundDeadline": "2026-09-19T23:59:59-03:00"
  },
  "customer": {
    "email": "cliente.teste@example.com",
    "name": "Cliente Teste",
    "phone": "+5511999999999",
    "document": "00000000000",
    "documentType": "CPF",
    "purchaseDate": "2026-09-12T06:28:41.925Z"
  },
  "product": {
    "id": "cmty05wl0000apyg1bx1566ej",
    "name": "Curso de Tráfego Pago 2026",
    "amount": 49900
  },
  "company": {
    "name": "Fixtures Empresa A",
    "document": "11111111000191",
    "documentType": "CNPJ"
  },
  "sale": {
    "type": "one_time",
    "subscriptionInterval": null,
    "renewalType": null,
    "mainOfferId": null,
    "mainOfferName": null,
    "orderBumpIds": [],
    "orderBumpOfferIds": [],
    "orderBumps": []
  }
}
```

<Tip>
  Filtre por `scope: "test"` no seu endpoint e descarte antes de liberar qualquer pedido. Um teste com `transaction.id: "test_transaction_id"` que libera acesso de verdade é um bug caro.
</Tip>

## 3. Investigue o que falhou

```bash theme={null}
curl -s "https://api.ephra.io/v1/webhook-deliveries?page=1&pageSize=3" \
  -H "Authorization: Bearer $TOKEN"
```

```json theme={null}
{
  "success": true,
  "data": [
    {
      "id": "3453856f-596f-493e-b2b0-86f0542f02d8",
      "transactionId": "cmtxyt3710000eag1smxe72ee",
      "type": "transaction",
      "event": "transaction_refunded",
      "eventType": "user",
      "status": "error",
      "url": "https://erp.minhaloja.com.br/integracoes/ephra",
      "retries": 1,
      "createdAt": "2026-09-12T05:50:09.057Z",
      "updatedAt": "2026-09-12T05:50:09.102Z"
    }
  ],
  "pagination": { "page": 1, "pageSize": 3, "total": 1, "totalPages": 1 }
}
```

`status: "error"` com `retries: 1` significa que a primeira tentativa falhou e a entrega está na fila de retentativa. `GET /v1/webhook-deliveries/{deliveryId}` traz o corpo enviado e a resposta recebida — é onde você descobre que o seu servidor devolveu `500` numa data inválida.

Para forçar um reenvio depois de corrigir o bug do seu lado:

```bash theme={null}
curl -s -X POST https://api.ephra.io/v1/webhook-deliveries/3453856f-596f-493e-b2b0-86f0542f02d8/retry \
  -H "Authorization: Bearer $TOKEN"
```

## 4. Segure o volume

Em pico de lançamento, `transaction_created` pode chegar às centenas por minuto. A supressão descarta parte das entregas de um evento, numa janela fixa.

```bash theme={null}
curl -s -X POST https://api.ephra.io/v1/webhook-suppressions \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "event": "transaction_created",
    "interval": 10,
    "suppressCount": 9,
    "isActive": true
  }'
```

```json theme={null}
{
  "success": true,
  "data": {
    "id": 1,
    "event": "transaction_created",
    "interval": 10,
    "suppressCount": 9,
    "counter": 0,
    "isActive": true,
    "createdAt": "2026-09-12T05:36:00.708Z",
    "updatedAt": "2026-09-12T05:36:00.708Z"
  }
}
```

Leia assim: a cada 10 disparos, 9 são descartados e 1 chega. `suppressCount` precisa ser menor que `interval`, e `interval` no mínimo 2. Omitir o `event` reprova:

```json theme={null}
{ "success": false, "message": "event: Required" }
```

<Warning>
  Supressão **perde eventos de verdade** — não é amostragem inteligente, é descarte por contagem. Nunca a use em `transaction_paid`, `transaction_refunded` ou `infraction`: são os eventos que liberam acesso e movem dinheiro. Reserve-a para `transaction_created` e outros sinais de volume.
</Warning>

## 5. Confira as chaves e o time

```bash theme={null}
curl -s https://api.ephra.io/v1/api-keys \
  -H "Authorization: Bearer $TOKEN"
```

```json theme={null}
{
  "success": true,
  "data": [
    {
      "id": 21,
      "type": "COMPANY",
      "publicKey": "cmtxzjidu0000isg1f7pzbisr",
      "active": true,
      "invalidatedAt": null,
      "createdAt": "2026-09-12T06:09:38.515Z",
      "updatedAt": "2026-09-12T06:09:38.515Z"
    },
    {
      "id": 16,
      "type": "COMPANY",
      "publicKey": "cmtxyq14b000014g1barehx03",
      "active": false,
      "invalidatedAt": "2026-09-12T06:09:38.507Z",
      "createdAt": "2026-09-12T05:46:43.116Z",
      "updatedAt": "2026-09-12T06:09:38.509Z"
    }
  ]
}
```

Chaves revogadas continuam na lista, com `active: false` e a data em `invalidatedAt` — serve de auditoria. O segredo nunca volta por esta rota: ele só existe na tela de criação, no painel.

```bash theme={null}
curl -s "https://api.ephra.io/v1/team-members?page=1&pageSize=5" \
  -H "Authorization: Bearer $TOKEN"
```

```json theme={null}
{
  "success": true,
  "data": [
    {
      "userId": "cmtxx1ua10000hlg1leidi9cj",
      "name": "Fixtures Produtor A",
      "email": "fixtures-company-a@public-api.invalid",
      "roles": ["user"],
      "participation": null
    }
  ],
  "pagination": { "page": 1, "pageSize": 5, "total": 1, "totalPages": 1 }
}
```

## 6. Confira o cadastro da empresa

Antes de um lançamento, vale conferir se a empresa está `active` — e pegar o próprio `companyId`, que nenhuma outra rota pede porque ele sai sempre do token.

```bash theme={null}
curl -s https://api.ephra.io/v1/company \
  -H "Authorization: Bearer $TOKEN"
```

```json theme={null}
{
  "success": true,
  "data": {
    "id": "cmtxx1uar0001hlg1u3ajbj6o",
    "name": "Doce Ofício Confeitaria Digital LTDA",
    "document": "19131243000197",
    "documentType": "CNPJ",
    "type": "company",
    "status": "active",
    "isActive": true,
    "isBlocked": false,
    "logoUrl": "https://documentos.ephra.io/companies/cmtxx1uar0001hlg1u3ajbj6o/logo.jpg?X-Amz-Expires=900",
    "description": "Cursos de confeitaria artesanal para quem vende doce em casa.",
    "address": {
      "zipCode": "01310930",
      "street": "Avenida Paulista",
      "number": "1578",
      "complement": "Conjunto 402",
      "neighborhood": "Bela Vista",
      "city": "São Paulo",
      "state": "SP",
      "country": "Brasil"
    },
    "createdAt": "2026-03-11T13:02:00.000Z",
    "updatedAt": "2026-09-10T17:44:00.000Z"
  }
}
```

`status: "active"` com `isBlocked: false` é a única combinação que vende. `logoUrl` é uma URL temporária assinada — baixe o arquivo, não guarde o link.

Mudou de sede? O endereço que sai em documento fiscal e o que a verificação de cadastro confere é este:

```bash theme={null}
curl -s -X PUT https://api.ephra.io/v1/company/address \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "zipCode": "01310930",
    "street": "Avenida Paulista",
    "number": "1578",
    "complement": "Conjunto 402",
    "neighborhood": "Bela Vista",
    "city": "São Paulo",
    "state": "SP"
  }'
```

Os campos omitidos ficam como estão.

O que o comprador vê da sua marca — o logo no checkout, no e-mail de confirmação e no perfil público — muda por outra rota. O logo vai em **base64**, num `data:` URL, e substitui o anterior:

```bash theme={null}
curl -s -X PUT https://api.ephra.io/v1/company/branding \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "logo": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==",
    "description": "Cursos de confeitaria artesanal para quem vende doce em casa."
  }'
```

```json theme={null}
{
  "success": true,
  "data": {
    "logoUrl": "https://documentos.ephra.io/companies/cmtxx1uar0001hlg1u3ajbj6o/logo_cmtxx1uar0001hlg1u3ajbj6o.png?X-Amz-Expires=900",
    "description": "Cursos de confeitaria artesanal para quem vende doce em casa."
  }
}
```

São aceitos `image/png`, `image/jpeg`, `image/jpg` e `image/webp`. Uma URL comum no lugar do `data:` URL é recusada na validação, com os formatos esperados na própria mensagem:

```json theme={null}
{ "success": false, "message": "logo: logo deve ser um data URL base64 (image/jpeg, image/jpg, image/png, image/webp)" }
```

`logo: null` remove o logo atual e `description: null` apaga a descrição — omitir o campo, ao contrário, não mexe nele. A `logoUrl` que volta é assinada e temporária, como a de `GET /v1/company`: serve para conferir o envio, não para colar num site.

Os documentos de verificação já enviados saem em URLs temporárias do mesmo tipo:

```bash theme={null}
curl -s https://api.ephra.io/v1/company/documents \
  -H "Authorization: Bearer $TOKEN"
```

```json theme={null}
{
  "success": true,
  "data": {
    "documentUrl": null,
    "companyConstitution": null,
    "proofOfAddress": null,
    "proofOfBankAccount": null
  }
}
```

`null` não é erro: significa que aquele documento ainda não foi enviado. Quatro `null` numa empresa que não consegue sacar é exatamente o diagnóstico da próxima seção.

O envio é a mesma rota com `PUT`, e cada arquivo também vai em base64 — aqui `application/pdf` entra na lista de formatos, com teto de 5 MB por arquivo:

```bash theme={null}
curl -s -X PUT https://api.ephra.io/v1/company/documents \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "documentUrl": "data:application/pdf;base64,JVBERi0xLjQKJcfsj6IKNSAwIG9iago8PC9MZW5ndGggNiAwIFI+PgpzdHJlYW0K",
    "proofOfAddress": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="
  }'
```

```json theme={null}
{
  "success": true,
  "data": {
    "documentUrl": "https://documentos.ephra.io/companies/cmtxx1uar0001hlg1u3ajbj6o/document_cmtxx1uar0001hlg1u3ajbj6o.pdf?X-Amz-Expires=900",
    "companyConstitution": null,
    "proofOfAddress": "https://documentos.ephra.io/companies/cmtxx1uar0001hlg1u3ajbj6o/proof_of_address_cmtxx1uar0001hlg1u3ajbj6o.png?X-Amz-Expires=900",
    "proofOfBankAccount": null
  }
}
```

Os nomes dos campos não são óbvios, e são os mesmos na leitura e no envio:

| Campo                 | Documento                     |
| --------------------- | ----------------------------- |
| `documentUrl`         | Cartão CNPJ                   |
| `companyConstitution` | Contrato social               |
| `proofOfAddress`      | Comprovante de endereço       |
| `proofOfBankAccount`  | Comprovante de conta bancária |

A resposta traz os quatro depois da gravação, então dá para conferir o que ainda falta sem uma segunda chamada. Os documentos que ficaram de fora do corpo continuam como estavam, e reenviar um substitui o anterior. É este envio que resolve o `needsCompanyDocuments: true` da próxima seção.

## 7. Descubra por que o saque não sai

Saque recusado quase nunca é problema de saldo: é a subconta no provedor de pagamento, que ainda não terminou de ser aprovada. Esta leitura nunca cria nem avança nada — pode chamar à vontade.

```bash theme={null}
curl -s https://api.ephra.io/v1/subaccount-status \
  -H "Authorization: Bearer $TOKEN"
```

```json theme={null}
{
  "success": true,
  "data": {
    "applicable": true,
    "isNewCompany": true,
    "outcome": "ok",
    "provisioningStatus": "under_review",
    "status": "pending",
    "onboardingUrl": "https://onboarding.provedor.com.br/kyc/9f2c41ab",
    "needsCompanyDocuments": true,
    "hasCompanyConstitution": true
  }
}
```

Leia de cima para baixo:

| Campo                    | O que dizer ao vendedor                                                             |
| ------------------------ | ----------------------------------------------------------------------------------- |
| `applicable: false`      | A empresa não opera por subconta. Os outros campos vêm neutros e não há o que fazer |
| `status`                 | `pending`, `active`, `inactive` ou `blocked` — **só `active` libera saque**         |
| `provisioningStatus`     | A etapa relatada pelo provedor, como `under_review` ou `pix_key_provisioning`       |
| `onboardingUrl`          | Falta preencher algo no provedor; mande o vendedor para este link                   |
| `needsCompanyDocuments`  | Falta o cartão CNPJ, e a aprovação não anda sem ele                                 |
| `hasCompanyConstitution` | `true` quando o contrato social já está guardado — não peça de novo                 |
| `outcome: "none"`        | Ainda não existe subconta; `error` significa que o provedor falhou na consulta      |

Numa empresa que não opera por subconta a resposta é toda neutra, e é assim que deve ser:

```json theme={null}
{
  "success": true,
  "data": {
    "applicable": false,
    "isNewCompany": false,
    "outcome": "none",
    "provisioningStatus": null,
    "status": null,
    "onboardingUrl": null,
    "needsCompanyDocuments": false,
    "hasCompanyConstitution": false
  }
}
```

Depois que o vendedor corrigiu o cadastro ou reenviou um documento, peça a reavaliação:

```bash theme={null}
curl -s -X POST https://api.ephra.io/v1/subaccount-status/resolve \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'
```

A resposta tem o mesmo formato da consulta, já com a situação resultante. A rota é idempotente: cria a subconta se ainda não existir e, do contrário, apenas avança a que existe.

<Warning>
  `resolve` é escrita, e conversa com o provedor a cada chamada. Use depois de uma correção, não em laço de polling — para acompanhar sem escrever nada, `GET /v1/subaccount-status` responde a mesma coisa.
</Warning>

## Todas as operações do domínio

<CardGroup cols={2}>
  <Card title="Webhooks" icon="bell" href="/api-reference/plataforma/listar-webhooks">
    Cadastrar, atualizar, testar e excluir endpoints.
  </Card>

  <Card title="Entregas" icon="paper-plane" href="/api-reference/plataforma/listar-entregas-de-webhook">
    Histórico, detalhe de cada tentativa e reenvio.
  </Card>

  <Card title="Supressões" icon="filter" href="/api-reference/plataforma/listar-supressões-de-webhook">
    Regras de descarte por evento e janela.
  </Card>

  <Card title="Chaves e equipe" icon="key" href="/api-reference/plataforma/listar-chaves-de-api">
    Chaves de API ativas e revogadas, membros e convites.
  </Card>

  <Card title="Empresa e subconta" icon="building" href="/api-reference/plataforma/consultar-dados-da-empresa">
    Cadastro, endereço, marca, documentos e a situação que libera saque.
  </Card>

  <Card title="Integrações" icon="plug" href="/guias/integracoes">
    Aplicativos externos, canais de comunidade e entrega automática.
  </Card>
</CardGroup>
