Empty request body when receiving Mailgun webhook data

I’m working on a Node.js application that needs to process webhook notifications from Mailgun when emails bounce. The issue is that when I test the webhook using Mailgun’s test feature, my server receives an empty request body instead of the expected data.

When I send the same webhook to Mailgun’s Postbin testing service, all the data appears correctly. But when it hits my local development server, I only get an empty object.

I’m building a simple Express.js service to handle these bounce notifications and alert administrators. Here’s my current setup:

const express = require('express');
const server = express();
const parser = require('body-parser');
const mg = require('mailgun-js')({apiKey: 'key-abc123', domain: 'mydomain.mailgun.org'});

server.use(parser.urlencoded({
  extended: false
}));

function setupRoutes(server) {
  server.post('/hooks/*', function (request, response, next) {
    const payload = request.body;

    if (!mg.validateWebhook(payload.timestamp, payload.token, payload.signature)) {
      console.error('Unauthorized request received');
      response.send({ error: { message: 'Signature validation failed' } });
      return;
    }

    next();
  });

  server.post('/hooks/bounce/', function (request, response) {
    console.log("Processing webhook data");
    console.log(request.body); // This shows empty object
    response.send("received");
  });
}

server.listen(3000, function(){
  setupRoutes(server);
  console.log("Server running on port 3000");
});

The webhook URL I’m using for testing is my public IP with port 3000. I’m not sure what I’m missing that would cause the request body to be empty when it works fine with Postbin.

I encountered the same problem. Mailgun webhooks typically use application/x-www-form-urlencoded, but the data structure can sometimes be unconventional. To troubleshoot, you should implement some logging immediately before your route handlers to inspect the incoming requests.

Ensure your server is accessible from the internet; I struggled with port forwarding and found ngrok to be a much simpler solution for local testing. An empty request body can indicate that your body parser isn’t configured correctly for the content type or that the request isn’t reaching your middleware. Consider utilizing express.raw() middleware to capture any data that the urlencoded parser might overlook, and verify that your webhook URL in Mailgun’s settings aligns perfectly with your server route.

Check if you’re using HTTPS - Mailgun sometimes rejects HTTP connections. Also try adding express.text() middleware with your urlencoded parser. I had the same issue and it was the content-type header not matching what body-parser expected. Quick fix: log req.headers to see what’s actually coming through.

Your middleware order is crucial here. By calling setupRoutes(server) within the listen callback, you’re registering your routes after the server starts. This means that the body-parser middleware won’t process incoming requests appropriately. Move the setupRoutes(server) call to execute before server.listen(). Remember, middleware in Express runs in the order it’s defined, so if the routes are registered before the body-parser, you’ll end up with an empty request body. Additionally, consider adding some debugging middleware to log the raw request data for better insight into what’s reaching your server. Sometimes Mailgun sends data with a Content-Type that body-parser doesn’t handle, so including express.json() might also help accommodate various content types.