Integração com webhooks de pagamento na loja do seu servidor de MU Online
Configure webhooks de pagamento (Mercado Pago, PagSeguro, Stripe) na loja online do seu servidor de MU Online, com validação de assinatura, idempotência e crédito automático de VIP/Zen/Cash Points.
A loja de Cash Points é uma das principais fontes de receita de um servidor de MU Online privado, e o elo mais frágil dela costuma ser justamente a confirmação de pagamento. Depender só da tela de "pagamento aprovado" no navegador do jogador é receita para chargeback, fraude e créditos duplicados. A
A loja de Cash Points é uma das principais fontes de receita de um servidor de MU Online privado, e o elo mais frágil dela costuma ser justamente a confirmação de pagamento. Depender só da tela de "pagamento aprovado" no navegador do jogador é receita para chargeback, fraude e créditos duplicados. A solução correta é integrar webhooks: notificações assíncronas que o gateway de pagamento envia diretamente para o seu servidor, confirmando que o dinheiro realmente entrou. Este tutorial cobre a configuração de webhooks com Mercado Pago, PagSeguro e Stripe, a validação de assinatura, o tratamento de idempotência e o crédito automático de VIP, Zen ou Cash Points na conta do jogador, com os erros mais comuns de quem implementa isso pela primeira vez.
Por que confirmação via navegador não é suficiente
Quando o jogador finaliza o pagamento, o gateway redireciona o navegador dele para uma URL de "sucesso" no seu site. Esse redirecionamento não é confiável como fonte de verdade: o jogador pode fechar a aba antes de completar, perder conexão, ou em casos raros manipular a URL de retorno tentando forjar um ?status=approved. O webhook, em contraste, é uma chamada HTTP servidor-a-servidor, assinada criptograficamente, disparada pelo próprio gateway quando o status do pagamento muda de verdade no lado deles.
Visão geral do fluxo completo
- Jogador escolhe um pacote de Cash Points na loja web e é redirecionado ao checkout do gateway.
- Gateway processa o pagamento (Pix, cartão, boleto).
- Gateway envia um webhook POST para uma URL sua, informando o status da transação.
- Seu backend valida a assinatura, verifica idempotência e credita o valor na tabela de créditos do jogador (lida pelo servidor de MU no login ou via comando in-game).
- Gateway também redireciona o navegador do jogador para a página de sucesso — só como UX, não como fonte de verdade.
Comparando os principais gateways usados por servidores de MU brasileiros
| Gateway | Header de assinatura | Formato do payload | Observação |
|---|---|---|---|
| Mercado Pago | x-signature (HMAC SHA256) | JSON com data.id do pagamento | Precisa buscar o pagamento completo via API após o webhook |
| PagSeguro/PagBank | Token na URL de notificação | XML/JSON dependendo da API (legada vs. nova) | API legada envia só o código; API nova envia payload completo |
| Stripe | Stripe-Signature (HMAC SHA256) | JSON completo do evento | Biblioteca oficial já valida a assinatura (stripe.webhooks.constructEvent) |
| Pagar.me | x-hub-signature (HMAC SHA1) | JSON completo | Formato similar ao Mercado Pago |
Passo 1 — Criar o endpoint que recebe o webhook
Use uma rota dedicada, fora de qualquer autenticação de sessão de usuário (o gateway não tem cookie de sessão):
<?php
// webhook_pagamento.php
$payload = file_get_contents('php://input');
$headers = getallheaders();
// nunca confie no payload antes de validar a assinatura
if (!validarAssinatura($payload, $headers['x-signature'] ?? '')) {
http_response_code(401);
exit('assinatura inválida');
}
$evento = json_decode($payload, true);
processarEvento($evento);
http_response_code(200);
Passo 2 — Validar a assinatura (HMAC)
Nunca processe um webhook sem confirmar que ele veio mesmo do gateway. O princípio é o mesmo em todos: recalcular o HMAC com sua chave secreta e comparar com o header recebido, usando comparação de tempo constante para evitar timing attack:
function validarAssinatura(string $payload, string $assinaturaRecebida): bool {
$chaveSecreta = getenv('WEBHOOK_SECRET');
$assinaturaCalculada = hash_hmac('sha256', $payload, $chaveSecreta);
return hash_equals($assinaturaCalculada, $assinaturaRecebida);
}
Guarde a WEBHOOK_SECRET em variável de ambiente, nunca no código-fonte versionado.
Passo 3 — Buscar o status real da transação (quando necessário)
Alguns gateways (Mercado Pago é o caso clássico) enviam só um ID no webhook, e você precisa consultar a API deles para confirmar o status real — isso evita que um webhook falsificado com status "approved" seja aceito sem checagem cruzada:
$paymentId = $evento['data']['id'];
$resposta = httpGet("https://api.mercadopago.com/v1/payments/{$paymentId}", [
'Authorization: Bearer ' . getenv('MP_ACCESS_TOKEN')
]);
$pagamento = json_decode($resposta, true);
if ($pagamento['status'] !== 'approved') {
http_response_code(200); // reconhece o evento, mas não credita
exit;
}
Passo 4 — Garantir idempotência
Gateways reenviam o mesmo webhook várias vezes se não recebem 200 a tempo, ou por política de retry. Registre o ID da transação antes de creditar, e recuse duplicatas:
CREATE TABLE webhook_eventos (
id_transacao VARCHAR(100) PRIMARY KEY,
processado_em DATETIME,
valor_creditado DECIMAL(10,2)
);
if (transacaoJaProcessada($paymentId)) {
http_response_code(200);
exit; // já creditado, evita duplicar
}
Passo 5 — Creditar VIP, Zen ou Cash Points
Depois de validado e confirmado como não duplicado, credite na tabela que o GameServer lê (diretamente no banco do MU, ou em uma tabela intermediária que um serviço credita periodicamente):
UPDATE AccountCash SET CashPoints = CashPoints + @valorComprado
WHERE AccountID = @conta;
INSERT INTO webhook_eventos (id_transacao, processado_em, valor_creditado)
VALUES (@paymentId, GETDATE(), @valorComprado);
Se o seu emulador usa Web Cash Shop com tabela própria (ex.: IGCN_CashShop), credite na estrutura correspondente e garanta que o GameServer releia o saldo no próximo login ou via evento em tempo real, se suportado.
Passo 6 — Notificar o jogador
Envie e-mail de confirmação e, se possível, uma notificação in-game (mala postal, mensagem de sistema) confirmando o crédito. Isso reduz drasticamente tickets de suporte do tipo "paguei e não recebi", já que muitos casos são apenas atraso de alguns segundos entre pagamento e webhook.
Testando webhooks em ambiente local
Gateways não conseguem alcançar localhost. Use um túnel público durante o desenvolvimento:
ngrok http 8443
# copie a URL https gerada e cadastre como endpoint de webhook no painel do gateway
Todos os gateways citados oferecem modo sandbox/teste — use cartões e contas de teste antes de apontar para produção.
Segurança adicional: whitelist de IP e rate limit
Complementarmente à validação de assinatura, restrinja o endpoint de webhook por IP de origem quando o gateway publica uma lista fixa (Stripe e PagSeguro publicam ranges), e aplique rate limiting no endpoint para mitigar tentativas de força bruta contra a validação de assinatura.
Logs e auditoria
Grave todo payload recebido (mesmo os rejeitados por assinatura inválida) em um log separado, com timestamp e IP de origem. Isso é essencial para investigar disputas de chargeback e para auditar se algum crédito foi aplicado indevidamente.
Erros comuns e soluções
| Sintoma | Causa provável | Solução |
|---|---|---|
| Cash Points creditados em dobro | Webhook reenviado sem checagem de idempotência | Registrar ID de transação e ignorar duplicatas |
| Webhook nunca chega | Endpoint sem HTTPS válido ou URL errada no painel | Configurar certificado válido e revisar URL cadastrada no gateway |
| Assinatura sempre inválida | Chave secreta errada ou payload alterado antes da validação (ex. framework fazendo parse) | Validar sobre o raw body, antes de qualquer decodificação |
| Jogador pagou mas não recebeu | Falha no crédito retornou 200 ao gateway por engano | Retornar 500 em falha de crédito para forçar reenvio |
| Webhook de teste (sandbox) processado em produção | Ambiente sandbox e produção compartilhando o mesmo endpoint sem distinção | Usar endpoints/chaves separadas para sandbox e produção |
| Chargeback não é refletido no jogo | Falta de tratamento do evento de estorno/chargeback | Implementar handler para eventos de reembolso e remover crédito/aplicar banimento conforme política |
Checklist de integração de pagamentos
- Endpoint HTTPS dedicado para webhooks, fora da autenticação de sessão normal.
- Validação de assinatura HMAC implementada e testada com payload real.
- Consulta de status real via API do gateway quando o webhook só traz um ID.
- Tabela de idempotência registrando IDs de transação já processados.
- Crédito de VIP/Zen/Cash Points automatizado e validado em sandbox.
- Notificação ao jogador (e-mail e/ou in-game) após crédito.
- Logs de todos os payloads recebidos, incluindo os rejeitados.
- Tratamento de eventos de estorno/chargeback implementado.
Com os webhooks confiáveis e a loja creditando automaticamente, vale revisar a arquitetura geral do site e do servidor para garantir que o restante da infraestrutura suporta o volume de transações com segurança — comece pelo guia de criação de servidor de MU Online se ainda não revisou a base.
Perguntas frequentes
Por que não posso só confiar no redirecionamento de sucesso do pagamento?
Porque o redirecionamento acontece no navegador do jogador, que pode ser manipulado, interrompido ou nunca completado (fechar a aba, queda de conexão). O webhook vem direto do servidor do gateway de pagamento para o seu backend, de forma assíncrona e confiável, e é a única fonte de verdade para liberar crédito.
Como sei que o webhook realmente veio do gateway de pagamento e não de um golpista?
Todo gateway sério assina o payload com uma chave secreta (HMAC) ou envia um header de assinatura. Você recalcula essa assinatura no seu backend com sua chave secreta e compara — se não bater, descarta a requisição. Nunca processe um webhook sem essa validação.
O que é idempotência e por que ela é obrigatória aqui?
É garantir que processar o mesmo evento duas vezes não credite Cash Points em dobro. Gateways reenviam webhooks quando não recebem confirmação a tempo, então você precisa registrar o ID do evento/transação e ignorar duplicatas antes de creditar.
Preciso de HTTPS para receber webhooks?
Sim, é exigência de praticamente todos os gateways (Mercado Pago, Stripe, PagSeguro) — eles recusam ou avisam sobre endpoints HTTP. Use um certificado válido (Let's Encrypt é suficiente) no domínio da sua loja.
O que fazer se o crédito falhar depois que o pagamento já foi aprovado?
Registre a falha em uma fila de reprocessamento e retorne HTTP 500 ao gateway para que ele reenvie o webhook automaticamente. Nunca retorne 200 se o crédito não foi aplicado — isso faz o gateway parar de tentar e o jogador fica sem receber.