O maior portal de MU Online do Brasil — desde 2003
Tutorial Avançado Web

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.

GA Gabriel · Atualizado em 21 abr 2024 · ⏱ 16 min de leitura
Resposta rápida

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

  1. Jogador escolhe um pacote de Cash Points na loja web e é redirecionado ao checkout do gateway.
  2. Gateway processa o pagamento (Pix, cartão, boleto).
  3. Gateway envia um webhook POST para uma URL sua, informando o status da transação.
  4. 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).
  5. 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

GatewayHeader de assinaturaFormato do payloadObservação
Mercado Pagox-signature (HMAC SHA256)JSON com data.id do pagamentoPrecisa buscar o pagamento completo via API após o webhook
PagSeguro/PagBankToken na URL de notificaçãoXML/JSON dependendo da API (legada vs. nova)API legada envia só o código; API nova envia payload completo
StripeStripe-Signature (HMAC SHA256)JSON completo do eventoBiblioteca oficial já valida a assinatura (stripe.webhooks.constructEvent)
Pagar.mex-hub-signature (HMAC SHA1)JSON completoFormato 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

SintomaCausa provávelSolução
Cash Points creditados em dobroWebhook reenviado sem checagem de idempotênciaRegistrar ID de transação e ignorar duplicatas
Webhook nunca chegaEndpoint sem HTTPS válido ou URL errada no painelConfigurar certificado válido e revisar URL cadastrada no gateway
Assinatura sempre inválidaChave 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 recebeuFalha no crédito retornou 200 ao gateway por enganoRetornar 500 em falha de crédito para forçar reenvio
Webhook de teste (sandbox) processado em produçãoAmbiente sandbox e produção compartilhando o mesmo endpoint sem distinçãoUsar endpoints/chaves separadas para sandbox e produção
Chargeback não é refletido no jogoFalta de tratamento do evento de estorno/chargebackImplementar 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.

GA
Editor de guias e builds

Gabriel cobre gameplay, builds de classes, PvP e progressão. Testa cada estratégia em servidor antes de publicar.

Continue lendo

Artigos relacionados