Agent skill

Mailjet Webhooks Skill

Receive and authenticate Mailjet webhooks. Use when setting up Mailjet event webhook handlers, securing them with Basic auth credentials in the webhook URL, or handling email events like sent, open, click, bounce, blocked, spam, and unsub.

Install this skill

npx skills add hookdeck/webhook-skills --skill mailjet-webhooks


When to Use This Skill

  • How do I receive Mailjet webhooks (Event API / Event tracking)?
  • How do I verify or secure a Mailjet webhook? (there is no signature)
  • Why is my Mailjet webhook endpoint receiving an Authorization: Basic header?
  • How do I handle sent, open, click, bounce, blocked, spam, and unsub events?
  • Why does my Mailjet webhook receive an array of events instead of one object?
  • Why is Mailjet retrying my webhook?

How Mailjet Webhooks Are Secured

Mailjet does not sign webhooks and cannot send a custom header. The only built-in protection is HTTP Basic auth: put credentials in the webhook URL you register, and Mailjet sends them as an Authorization: Basic base64(username:password) header.

https://username:password@your-domain.com/webhooks/mailjet

Always use HTTPS, and use a long random password. Your handler must decode the header and compare the username and password in constant time.

Verification (core)

const crypto = require('crypto');

// Hash both sides so unequal lengths don't throw or leak length
const safeEqual = (a, b) => crypto.timingSafeEqual(
  crypto.createHash('sha256').update(a).digest(),
  crypto.createHash('sha256').update(b).digest());

function verifyMailjet(authHeader, username, password) {
  if (!authHeader || !/^Basic /i.test(authHeader)) return false;
  const decoded = Buffer.from(authHeader.slice(6).trim(), 'base64').toString('utf8');
  const i = decoded.indexOf(':');               // split on FIRST colon only
  if (i < 0) return false;
  const userOk = safeEqual(decoded.slice(0, i), username);
  const passOk = safeEqual(decoded.slice(i + 1), password);
  return userOk && passOk;                      // no short-circuit between the two
}
// Reject with 401 + WWW-Authenticate: Basic when this returns false

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Payload Shape

The body is JSON. By default it is one event object per request. If you enable Group Events (Event tracking settings, or version: 2 on /eventcallbackurl), Mailjet posts a JSON array of events. Handlers should accept both.

Every event has event, time, email, mj_campaign_id, mj_contact_id, customcampaign, MessageID, CustomID, and Payload.

Common Event Types

EventTriggered WhenKey Fields
sentDestination SMTP server accepted the messagesmtp_reply, mj_message_id
openRecipient opened the emailip, geo, agent
clickRecipient clicked a tracked linkurl, ip, geo, agent
bounceDelivery failedhard_bounce, blocked, error_related_to, error, comment
blockedMailjet or the recipient side blocked the senderror_related_to, error
spamRecipient reported the message as spamsource
unsubRecipient unsubscribedmj_list_id, ip, geo, agent

For the full event reference, see Mailjet event types.

Environment Variables

# Must match the user:pass embedded in the webhook URL registered with Mailjet
MAILJET_WEBHOOK_USERNAME=mailjet
MAILJET_WEBHOOK_PASSWORD=change-me-to-a-long-random-string

Gotchas

  • Respond 200 quickly. Any other status makes Mailjet retry every 30 seconds for up to 24 hours, and a persistently failing URL can be suspended (reactivate by updating it with a new URL). Process events asynchronously.
  • No event ID or timestamp-based replay protection. Mailjet retries and sent volume make duplicates likely; dedupe on MessageID + event + time (+ email).
  • Passwords may contain :. Split the decoded credentials on the first colon only. Percent-encode special characters in the URL when registering it.
  • sent is high-volume. Turn on Group Events so you receive batches.
  • bounce events with hard_bounce: false are temporary; only suppress addresses on hard bounces.

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 mailjet --path /webhooks/mailjet

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 2, 2026

View on GitHub →