Desenvolvedores · Documentação da API

Avisos automáticos

Avisos automáticos (webhooks) do EduPay: eventos, assinatura HMAC, novas tentativas e exemplos em PHP e Node.js.

Com os avisos automáticos (webhooks), o EduPay chama o seu endereço quando algo acontece na escola: aluno cadastrado, matrícula ativada, contrato assinado, cobrança emitida, parcela paga ou vencida. Assim o seu sistema fica em dia sem consultar a API de tempos em tempos.

Cadastrar o endereço

Pelo painel: Integrações → Avisos automáticos. Ou pela API, com uma chave que tenha webhooks:manage:

curl -X POST https://edupay.estudiosite.com/api/v1/webhooks \
  -H "Authorization: Bearer $EDUPAY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://erp.escola.com.br/edupay/avisos",
    "description": "ERP financeiro",
    "events": ["installment.paid", "installment.overdue"]
  }'
  • A resposta traz o segredo (secret, começa com whsec_) uma vez só. Guarde-o: é com ele que você confere que o aviso veio do EduPay.
  • O endereço precisa ser https e público. Endereço de rede interna é recusado.
  • Até 10 endereços por escola. Em events, liste os eventos ou use ["*"] para todos.
  • Os avisos só saem com a API da escola ligada. O envio de teste funciona sempre.

Eventos

Evento Quando acontece Vem em data
student.created Aluno cadastrado (painel, página de vendas, API) student
student.updated Nome, documento, contato, endereço, situação ou código externo alterado student
enrollment.created Matrícula criada (painel, página de vendas, API) enrollment, student
enrollment.activated Matrícula ficou ativa (contrato assinado ou primeiro pagamento) enrollment, student
enrollment.suspended Acesso ao Moodle bloqueado por atraso enrollment, student
enrollment.reactivated Acesso ao Moodle liberado depois do pagamento enrollment, student
enrollment.cancelled Matrícula cancelada enrollment, student
enrollment.completed Matrícula concluída enrollment, student
contract.signed Aluno assinou, ou documento em papel aprovado contract, student
installment.charge_issued Boleto, PIX ou link de cartão gerado installment, student
installment.charge_cancelled Cobrança cancelada (a parcela continua em aberto) installment, student
installment.paid Pagamento confirmado: banco, cartório ou baixa manual installment, student
installment.overdue Parcela venceu sem pagamento (rotina diária, de madrugada) installment, student
webhook.test Botão "Enviar teste" ou POST /webhooks/{id}/test mensagem de teste

enrollment, contract e installment vêm completos, no mesmo formato da API. Em student vêm id, external_id, full_name e email (nos eventos de aluno, o cadastro completo).

Formato do aviso

POST /edupay/avisos HTTP/1.1
Content-Type: application/json
User-Agent: EduPay-Webhooks/1.0
X-Edupay-Event: installment.paid
X-Edupay-Event-Id: evt_01jbx9m3k2t8f4c6v0w5y7z9qa
X-Edupay-Delivery: 8812
X-Edupay-Signature: t=1793197800,v1=5f2b9c...e81a

{
  "id": "evt_01jbx9m3k2t8f4c6v0w5y7z9qa",
  "type": "installment.paid",
  "created_at": "2026-11-10T13:10:00Z",
  "api_version": "v1",
  "data": {
    "installment": { "id": 9813, "status": "paid", "amount": 45000, "paid_amount": 45000, "currency": "BRL", "...": "..." },
    "student": { "id": 532, "external_id": "ALU-0091", "full_name": "Maria Souza", "email": "maria@exemplo.com" }
  }
}

Conferir a assinatura

O cabeçalho X-Edupay-Signature traz t (horário, em segundos) e v1: o HMAC-SHA256, com o segredo, de t + . + o corpo exatamente como chegou. Confira antes de qualquer outra coisa e recuse aviso com mais de 5 minutos.

PHP:

$secret    = getenv('EDUPAY_WEBHOOK_SECRET');
$body      = file_get_contents('php://input');           // o corpo cru, sem decodificar
parse_str(str_replace(',', '&', $_SERVER['HTTP_X_EDUPAY_SIGNATURE'] ?? ''), $sig);

$expected = hash_hmac('sha256', ($sig['t'] ?? '') . '.' . $body, $secret);

if (! hash_equals($expected, $sig['v1'] ?? '') || abs(time() - (int) ($sig['t'] ?? 0)) > 300) {
    http_response_code(400);
    exit;
}

$event = json_decode($body, true);
// ... grave $event['id'] e processe ...
http_response_code(200);

Node.js (Express):

const crypto = require('crypto');

app.post('/edupay/avisos', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = Object.fromEntries(
    (req.get('X-Edupay-Signature') || '').split(',').map((p) => p.split('='))
  );
  const expected = crypto
    .createHmac('sha256', process.env.EDUPAY_WEBHOOK_SECRET)
    .update(`${sig.t}.${req.body}`)
    .digest('hex');

  const ok = sig.v1 && sig.v1.length === expected.length
    && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig.v1))
    && Math.abs(Date.now() / 1000 - Number(sig.t)) <= 300;

  if (!ok) return res.sendStatus(400);

  const event = JSON.parse(req.body);
  // ... grave event.id e processe ...
  res.sendStatus(200);
});

Responder e processar

  • Responda 2xx em até 10 segundos. Redirecionamento conta como falha. Se o processamento é demorado, grave o aviso, responda 200 e processe depois.
  • O mesmo aviso pode chegar mais de uma vez (nova tentativa, reenvio pelo painel). Use o id do evento para não processar duas vezes.
  • A ordem de chegada não é garantida. Na dúvida, consulte o registro na API (GET /installments/{id}) para ter o estado atual.

Novas tentativas

Se o seu endereço não responder 2xx, o EduPay tenta de novo em 1 min, 5 min, 30 min, 2 h, 12 h e 24 h. Depois disso a entrega fica como falha e pode ser reenviada pelo painel.

Um endereço que passa 3 dias falhando, sem nenhum sucesso, é desligado, e o dono da conta da escola recebe um e-mail. Ele é religado pelo painel.

O histórico de entregas (situação, resposta e tentativas) fica 30 dias no painel e em GET /webhooks/{id}/deliveries.

Trocar o segredo

No painel, Trocar segredo gera um segredo novo, que passa a valer na hora. Atualize o seu sistema logo em seguida.