Visão geral

A MailGrid envia webhooks (HTTP POST) para a sua aplicação sempre que há atualização de envio. Use-os para sincronizar o status dos seus e-mails em tempo real.

MétodoPOST
Content-Typeapplication/json
AutenticaçãoAuthorization: Bearer <TOKEN> (token por webhook)

Eventos disponíveis

O campo status do payload identifica o tipo de evento:

statusEventoDescrição
1SucessoMensagem entregue com sucesso.
2SoftbounceFalha temporária na entrega.
0HardbounceFalha permanente na entrega.

Configurar no painel do cliente

  1. Acesse o menu Webhooks e clique em Adicionar Webhook.
  2. Informe a URL (recomendado https://), um e-mail para alertas (opcional, usado quando o endpoint retornar erro) e os eventos que deseja receber (ex.: Sucesso + Hardbounce).
  3. O painel exibirá um Token na lista de webhooks. Guarde-o em segurança — será exigido no cabeçalho Authorization.
  4. Salve e integre com a sua aplicação para testar, enviando um POST com o payload solicitado.

Dica

Você pode cadastrar múltiplos webhooks por cliente e escolher diferentes combinações de eventos para cada um.

Segurança & Autenticação

Bearer Token

Todos os envios incluem o cabeçalho:

Authorization: Bearer SEU_TOKEN_AQUI
User-Agent: MailGrid-Webhook/1.0

Seu servidor deve validar o token e responder 401 se estiver inválido.

HTTPS recomendado

Proteja seu endpoint com TLS. Rejeite tráfego http:// sempre que possível.

Idempotência

Tratamos idempotência no provedor, mas implemente do seu lado também usando msgid como chave para evitar processamento duplicado.

Formato do Payload

Exemplo de JSON enviado

{
  "msgid": "1q2w3e4r5t-ABC",
  "status": 1,
  "email_de": "no-reply@seu-dominio.com",
  "email_para": "cliente@destino.com",
  "mensagem": "Entregue com sucesso",
  "data_envio": "2025-08-25 14:05:36",
  "data_entrega": "2025-08-25 14:05:41",
  "sender": "mailer@seu-dominio.com",
  "sender_ip": "203.0.113.10",
  "sender_host": "mx1.seu-dominio.com",
  "delivery_ip": "198.51.100.22",
  "delivery_host": "gmail-smtp-in.l.google.com",
  "size": "12345"
}

Campos

CampoTipoDescrição
msgidstringID único da mensagem.
statusint0 = Hardbounce, 1 = Sucesso, 2 = Softbounce.
email_destringRemetente.
email_parastringDestinatário.
mensagemstringMensagem/erro do MTA.
data_enviodatetimeData/hora de aceitação.
data_entregadatetimeData/hora do status final.
senderstringUsuário/real sender.
sender_ipipIP do emissor (origem).
sender_hoststringHost do emissor.
delivery_ipipIP de entrega.
delivery_hoststringHost de entrega.
sizestringTamanho do e-mail (bytes).

Entrega & Respostas

  • Timeout: o endpoint deve responder rapidamente (< 5–10s).
  • Sucesso: qualquer 2xx é considerado entregue.
  • Falha: códigos não-2xx geram nova tentativa e (se configurado) um e-mail de alerta.
  • Retentativas: reenviamos enquanto o evento não for marcado como entregue (idempotência via msgid).
  • Teste: você pode disparar testes no painel e via curl.

Teste com cURL

curl -i -X POST "https://seu-endpoint.com/webhook" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer SEU_TOKEN_AQUI" \
  -d '{
    "msgid":"abc123",
    "status":1,
    "email_de":"a@b.com",
    "email_para":"c@d.com",
    "mensagem":"ok"
  }'

Exemplos por linguagem

Endpoints prontos para receber e validar o webhook nas linguagens mais comuns.

PHP

<?php
declare(strict_types=1);

// ===== CONFIG =====
const TOKEN_ESPERADO = 'SEU_TOKEN_AQUI';
const LOG_FILE = __DIR__ . '/webhook_log.txt';
// ==================

// Função para obter o header Authorization
function obterAuthHeader(): string {
    $headers = getallheaders();
    return $headers['Authorization'] ?? $_SERVER['HTTP_AUTHORIZATION'] ?? '';
}

header('Content-Type: application/json; charset=utf-8');

try {
    $auth = obterAuthHeader();
    if (!str_starts_with($auth, 'Bearer ')) {
        http_response_code(401);
        echo json_encode(['status' => 'erro', 'msg' => 'Token ausente']);
        exit;
    }
    $token = trim(substr($auth, 7));
    if ($token !== TOKEN_ESPERADO) {
        http_response_code(401);
        echo json_encode(['status' => 'erro', 'msg' => 'Token inválido']);
        exit;
    }

    $raw = file_get_contents('php://input');
    $payload = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);

    // Loga o payload recebido
    file_put_contents(LOG_FILE, date('Y-m-d H:i:s') . ' - ' . json_encode($payload) . PHP_EOL, FILE_APPEND | LOCK_EX);

    echo json_encode(['status' => 'ok', 'msgid' => $payload['msgid'] ?? null]);
} catch (JsonException) {
    http_response_code(400);
    echo json_encode(['status' => 'erro', 'msg' => 'JSON inválido']);
    exit;
} catch (Throwable $e) {
    http_response_code(500);
    echo json_encode(['status' => 'erro', 'msg' => 'Erro interno']);
    exit;
}
?>

Python (Flask)

from flask import Flask, request, jsonify
app = Flask(__name__)
TOKEN = "SEU_TOKEN_AQUI"

@app.route('/webhook', methods=['POST'])
def webhook():
    auth = request.headers.get('Authorization', '')
    if not auth.startswith('Bearer '):
        return jsonify({'status':'erro','msg':'Token ausente'}), 401
    if auth.split(' ',1)[1] != TOKEN:
        return jsonify({'status':'erro','msg':'Token inválido'}), 401

    try:
        payload = request.get_json(force=True)
    except Exception:
        return jsonify({'status':'erro','msg':'JSON inválido'}), 400

    print('Webhook recebido:', payload)
    return jsonify({'status':'ok','msgid':payload.get('msgid')}), 200

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Node.js (Express)

const express = require('express');
const app = express();
const TOKEN = 'SEU_TOKEN_AQUI';
app.use(express.json());

app.post('/webhook', (req, res) => {
  const auth = req.get('Authorization') || '';
  if (!auth.startsWith('Bearer ')) return res.status(401).json({status:'erro', msg:'Token ausente'});
  if (auth.slice(7) !== TOKEN)      return res.status(401).json({status:'erro', msg:'Token inválido'});

  console.log('Webhook:', req.body);
  return res.json({status:'ok', msgid:req.body.msgid});
});

app.listen(3000, () => console.log('listening on 3000'));

Java (Spring Boot)

import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import java.util.Map;

@RestController
public class WebhookController {
  private static final String TOKEN = "SEU_TOKEN_AQUI";

  @PostMapping("/webhook")
  public ResponseEntity<Object> receive(
      @RequestHeader(value = "Authorization", required = false) String auth,
      @RequestBody Map<String,Object> payload
  ) {
    if (auth == null || !auth.startsWith("Bearer "))
      return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of("status","erro","msg","Token ausente"));
    if (!auth.substring(7).equals(TOKEN))
      return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of("status","erro","msg","Token inválido"));

    System.out.println("Webhook: " + payload);
    return ResponseEntity.ok(Map.of("status","ok","msgid",payload.get("msgid")));
  }
}

C# (ASP.NET Core)

using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("webhook")]
public class WebhookController : ControllerBase
{
    private const string TOKEN = "SEU_TOKEN_AQUI";

    [HttpPost]
    public IActionResult Post([FromBody] Dictionary<string, object> payload)
    {
        var auth = Request.Headers["Authorization"].ToString();
        if (string.IsNullOrEmpty(auth) || !auth.StartsWith("Bearer "))
            return Unauthorized(new { status = "erro", msg = "Token ausente" });
        if (auth.Substring(7) != TOKEN)
            return Unauthorized(new { status = "erro", msg = "Token inválido" });

        Console.WriteLine($"Webhook: {System.Text.Json.JsonSerializer.Serialize(payload)}");
        return Ok(new { status = "ok", msgid = payload.ContainsKey("msgid") ? payload["msgid"] : null });
    }
}

Solução de problemas

Cabeçalho Authorization não chega ao PHP

Atenção

Alguns servidores não repassam o header Authorization ao PHP. Ajuste conforme o seu ambiente:

Nginx (FastCGI)

fastcgi_param HTTP_AUTHORIZATION $http_authorization;

Apache (.htaccess)

RewriteEngine On
RewriteCond %{HTTP:Authorization} .+
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

Recebo 401 Token inválido

  • Confirme que o token usado no header é o mesmo do painel (sem espaços).
  • Logue o valor bruto de Authorization para depurar.

Não grava o arquivo

  • Verifique permissões do diretório/usuário do webserver.
  • Use caminho absoluto e LOCK_EX no file_put_contents.

Boas práticas

  • Responda 200 OK rapidamente e faça processamento pesado em background.
  • Implemente idempotência usando msgid.
  • Não registre tokens em logs. Armazene-os com segurança.
  • Use HTTPS e rotacione tokens periodicamente.

Endereços IP

Certifique-se de que os IPs dos servidores de webhook estão na lista de permissões (whitelist) da sua infraestrutura:

142.93.196.37 18.228.229.224 167.71.166.14 174.138.191.22 216.219.88.202 23.227.191.66