1) Visão geral
A xMailer envia webhooks (HTTP POST) para sua aplicação sempre que há atualização de envio. Use-os para sincronizar status de e-mail em tempo real.
- Método: POST
- Content-Type: application/json
- Autenticação:
Authorization: Bearer <TOKEN>
(token por webhook) - Eventos (status):
0
= Hardbounce,1
= Sucesso,2
= Softbounce
2) Configurar no painel do cliente
- Acesse o menu Webhooks e clique em Adicionar Webhook.
- Informe:
- URL (recomendado
https://
) - E-mail para alertas (opcional, usado quando o endpoint retornar erro)
- Eventos que deseja receber (ex.: Sucesso + Hardbounce)
- URL (recomendado
- O painel exibirá um Token na lista de webhooks. Guarde-o em segurança — será exigido no cabeçalho
Authorization
. - Salve e use o botão Testar para um POST de validação.
Você pode cadastrar múltiplos webhooks por cliente e escolher diferentes combinações de eventos para cada um.
3) Segurança & Autenticação
3.1 Bearer Token
Todos os envios incluem o cabeçalho:
Authorization: Bearer SEU_TOKEN_AQUI
User-Agent: xMailer-Webhook/1.0
Seu servidor deve validar o token e responder 401
se estiver inválido.
3.2 HTTPS recomendado
Proteja seu endpoint com TLS. Rejeite tráfego http://
sempre que possível.
3.3 Idempotência
Tratamos idempotência no provedor, mas implemente do seu lado também usando msgid
como chave para evitar processamento duplicado.
4) Formato do Payload
4.1 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"
}
4.2 Campos
Campo | Tipo | Descrição |
---|---|---|
msgid | string | ID único da mensagem. |
status | int | 0=Hardbounce, 1=Sucesso, 2=Softbounce. |
email_de | string | Remetente. |
email_para | string | Destinatário. |
mensagem | string | Mensagem/erro do MTA. |
data_envio | datetime | Data/hora de aceitação. |
data_entrega | datetime | Data/hora do status final. |
sender | string | Usuário/real sender. |
sender_ip | ip | IP do emissor (origem). |
sender_host | string | Host do emissor. |
delivery_ip | ip | IP de entrega. |
delivery_host | string | Host de entrega. |
size | string | Tamanho do e-mail (bytes). |
5) 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"
}'
6) Exemplos por linguagem
6.1 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;
}
?>
6.2 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)
6.3 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'));
6.4 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")));
}
}
6.5 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 });
}
}
7) Solução de problemas
7.1 Cabeçalho Authorization não chega ao PHP
Nginx (FastCGI)
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
Apache (.htaccess)
RewriteEngine On
RewriteCond %{HTTP:Authorization} .+
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
7.2 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.
7.3 Não grava o arquivo
- Verifique permissões do diretório/usuário do webserver.
- Use caminho absoluto e
LOCK_EX
nofile_put_contents
.
8) 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.
9) Endereços IP
- Certifique-se de que os endereços IP dos servidores de webhook estão na lista de permissões de sua infra:
- 51.79.17.20, 51.81.109.61, 54.232.93.107 e 209.133.221.2
© xMailer — Guia técnico de webhooks. © Copyright 2025 - Xmailer® marca registrada de Hedder® Tecnologia e Serviços Ltda