Agent skill
Mailchimp Webhooks Skill
Receive and secure Mailchimp webhooks. Use when setting up Mailchimp webhook handlers, responding to Mailchimp's GET URL validation, securing the endpoint with a URL secret, or handling audience events like subscribe, unsubscribe, profile, upemail, cleaned, and campaign.
Install this skill
npx skills add hookdeck/webhook-skills --skill mailchimp-webhooks
When to Use This Skill
- Setting up Mailchimp webhook handlers
- How do I respond to Mailchimp's webhook URL validation (the GET request)?
- How do I secure Mailchimp webhooks (they are not HMAC-signed)?
- Handling audience events: subscribe, unsubscribe, profile, upemail, cleaned, campaign
- Parsing Mailchimp's
application/x-www-form-urlencodedpayloads
Verification (core)
Mailchimp does NOT sign its webhooks — there is no HMAC and no signature header. You secure the endpoint two ways, both described in Mailchimp's sync audience data with webhooks guide:
- URL validation (GET): When you save a webhook, Mailchimp sends a
GETto the URL to confirm it is reachable. Respond200— do not require the secret on GET. - Shared secret (POST): Put an unguessable secret in the webhook URL's query string (e.g.
https://your.app/webhooks/mailchimp?secret=…) and compare it on everyPOSTwith a timing-safe comparison. Always serve the endpoint over HTTPS.
Payloads are application/x-www-form-urlencoded with a top-level type field and data[...] fields.
Node:
const crypto = require('crypto');
// Timing-safe compare of the ?secret= query param against your stored secret.
function verifyMailchimpSecret(provided, expected) {
if (!provided || !expected) return false;
const a = Buffer.from(provided);
const b = Buffer.from(expected);
if (a.length !== b.length) return false; // avoid throw on length mismatch
return crypto.timingSafeEqual(a, b);
}
Python:
import hmac
# Timing-safe compare of the ?secret= query param against your stored secret.
def verify_mailchimp_secret(provided: str, expected: str) -> bool:
if not provided or not expected:
return False
return hmac.compare_digest(provided, expected)
For complete handlers with GET validation, form parsing, event dispatch, and tests, see:
Common Event Types
Dispatch on the top-level type field.
type | Triggered When | Key data fields |
|---|---|---|
subscribe | A contact joins the audience | id, list_id, email, email_type, merges, ip_opt, ip_signup |
unsubscribe | A contact leaves the audience | action (unsub/delete), reason (manual/abuse), id, list_id, email, campaign_id |
profile | A contact updates their profile | id, list_id, email, email_type, merges, ip_opt |
upemail | A contact changes their email address | list_id, new_id, new_email, old_email |
cleaned | An address is cleaned (bounces/spam) | list_id, campaign_id, reason (hard/abuse), email |
campaign | A campaign finishes sending | id, subject, status, reason, list_id |
For full event reference, see Mailchimp's webhook guide.
Environment Variables
# The unguessable secret you append to your webhook URL query string.
# Register the URL as: https://your.app/webhooks/mailchimp?secret=<this value>
MAILCHIMP_WEBHOOK_SECRET=a-long-random-hard-to-guess-string
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 mailchimp --path /webhooks/mailchimp
Reference Materials
- references/overview.md - Mailchimp webhook concepts and events
- references/setup.md - Dashboard configuration and getting the secret
- references/verification.md - Securing the endpoint (no signature)