Mailchimp webhooks send form-encoded POST data (not JSON) with specific structure. Understanding payload formats is critical for parsing and processing events correctly.
Content-Type: application/x-www-form-urlencoded
Structure:
type=subscribe
&fired_at=2025-01-24+15%3A30%3A00
&data[id]=8a25ff1d98
&data[email]=user@example.com
&data[email_type]=html
&data[ip_opt]=192.168.1.100
&data[ip_signup]=192.168.1.100
&data[list_id]=a6b5da1054
&data[merges][EMAIL]=user@example.com
&data[merges][FNAME]=John
&data[merges][LNAME]=Doe
&data[merges][INTERESTS]=Group1%2CGroup2
After Parsing (example in Node.js):
{
type: 'subscribe',
fired_at: '2025-01-24 15:30:00',
data: {
id: '8a25ff1d98',
email: 'user@example.com',
email_type: 'html',
ip_opt: '192.168.1.100',
ip_signup: '192.168.1.100',
list_id: 'a6b5da1054',
merges: {
EMAIL: 'user@example.com',
FNAME: 'John',
LNAME: 'Doe',
INTERESTS: 'Group1,Group2'
}
}
}
Description: Fires when a new subscriber joins your audience via signup form, API, or admin action.
Payload Structure:
{
type: 'subscribe',
fired_at: '2025-01-24 15:30:00',
data: {
id: '8a25ff1d98', // Unique subscriber ID (MD5 hash of lowercase email)
email: 'user@example.com', // Subscriber's email address
email_type: 'html', // Email format preference: 'html' or 'text'
ip_opt: '192.168.1.100', // IP address where subscriber confirmed (double opt-in)
ip_signup: '192.168.1.100', // IP address where subscriber initially signed up
list_id: 'a6b5da1054', // Audience/list ID
merges: {
EMAIL: 'user@example.com',
FNAME: 'John',
LNAME: 'Doe',
BIRTHDAY: '05/15',
PHONE: '+1-555-123-4567',
ADDRESS: {
addr1: '123 Main St',
city: 'San Francisco',
state: 'CA',
zip: '94105',
country: 'US'
},
INTERESTS: 'Group1,Group2' // Interest group memberships
}
}
}
Key Fields:
data.id- Unique identifier (use for idempotency checks)data.email- Subscriber's email addressdata.list_id- Identifies which audience the event belongs todata.ip_opt- IP where user confirmed subscription (important for compliance/GDPR)data.merges- Object containing all merge fields (custom fields you defined)data.merges.FNAME/data.merges.LNAME- Standard first/last name fieldsdata.merges.INTERESTS- Comma-separated list of interest groups subscriber joined
Use Cases:
- Send welcome email through custom ESP
- Add subscriber to CRM with signup source tracking
- Track conversion from specific landing pages
- Assign subscriber to sales rep based on custom fields
- Trigger onboarding workflow in automation platform
Important Notes:
- For double opt-in lists, webhook fires AFTER subscriber confirms (not at initial signup)
- For single opt-in lists, webhook fires immediately upon signup
- If "triggered by API" is enabled, this fires for API subscriptions too (can cause loops)
Description: Fires when a subscriber opts out of your audience via unsubscribe link, preferences page, or admin action.
Payload Structure:
{
type: 'unsubscribe',
fired_at: '2025-01-24 16:45:00',
data: {
id: '8a25ff1d98',
email: 'user@example.com',
email_type: 'html',
ip_opt: '192.168.1.100',
list_id: 'a6b5da1054',
campaign_id: 'c123456789', // Campaign ID that triggered unsubscribe (if applicable)
reason: 'I no longer want to receive these emails', // Optional: unsubscribe reason
merges: {
EMAIL: 'user@example.com',
FNAME: 'John',
LNAME: 'Doe',
// ... other merge fields
}
}
}
Key Fields:
data.campaign_id- If present, indicates which campaign email caused unsubscribedata.reason- Unsubscribe reason text (if subscriber provided feedback)data.action- May include 'unsub' or 'delete' (delete = hard delete from list)
Use Cases:
- Remove subscriber from other marketing channels (SMS, push notifications)
- Add to suppression list across multiple platforms
- Trigger exit survey or feedback form
- Update CRM status to "unsubscribed"
- Send to re-engagement workflow after 30 days
- Track unsubscribe reasons for campaign optimization
Important Notes:
- Subscriber data remains in Mailchimp even after unsubscribe (status changes to "unsubscribed")
- If subscriber later re-subscribes, you'll receive a new
subscribewebhook - Mailchimp compliance requires honoring unsubscribes immediately (don't continue emailing)
Description: Fires when a subscriber updates their profile information (name, custom fields, preferences).
Payload Structure:
{
type: 'profile',
fired_at: '2025-01-24 17:00:00',
data: {
id: '8a25ff1d98',
email: 'user@example.com',
email_type: 'html',
ip_opt: '192.168.1.100',
list_id: 'a6b5da1054',
merges: {
EMAIL: 'user@example.com',
FNAME: 'Jonathan', // Changed from 'John'
LNAME: 'Doe',
PHONE: '+1-555-987-6543', // Updated phone number
COMPANY: 'Acme Corp', // Added company name
// ... other merge fields
}
}
}
Key Fields:
data.merges- Contains ALL merge fields (including unchanged ones)- No indication of which specific field changed (compare with your database to detect changes)
Use Cases:
- Sync profile updates to CRM in real-time
- Update user records in your application database
- Track data quality (e.g., how many subscribers add phone numbers)
- Trigger workflows based on specific field changes (e.g., company added)
- Maintain data consistency across multiple platforms
Important Notes:
- Webhook contains ALL merge fields, not just the changed ones
- You must compare with existing data to detect what changed
- High frequency event if subscribers regularly update profiles
- Consider debouncing rapid updates (multiple profile changes in short time)
Description: Fires when Mailchimp marks a subscriber's email as invalid due to hard bounces, repeated soft bounces, or spam complaints.
Payload Structure:
{
type: 'cleaned',
fired_at: '2025-01-24 18:15:00',
data: {
id: '8a25ff1d98',
email: 'user@example.com',
email_type: 'html',
list_id: 'a6b5da1054',
campaign_id: 'c123456789', // Campaign that caused bounce (if applicable)
reason: 'hard', // 'hard' (invalid email) or 'abuse' (spam complaint)
merges: {
EMAIL: 'user@example.com',
FNAME: 'John',
LNAME: 'Doe',
// ... other merge fields
}
}
}
Key Fields:
data.reason- Why email was cleaned:'hard'(hard bounce),'abuse'(spam complaint),'other'data.campaign_id- Campaign that triggered the cleaning event (if applicable)
Use Cases:
- Remove invalid emails from other systems to maintain list hygiene
- Add to global suppression list across all email providers
- Alert admin team about potential data quality issues
- Track bounce rates and email validation accuracy
- Update CRM with "invalid email" status
- Trigger email validation re-check workflow
Important Notes:
- Cleaned subscribers cannot be re-subscribed via API (must manually archive first)
- Mailchimp automatically prevents sending to cleaned addresses
- Spam complaints (abuse) should trigger immediate suppression everywhere
- High cleaning rates indicate list quality problems or permission issues
Description: Fires when a subscriber changes their email address through Mailchimp's profile update page.
Payload Structure:
{
type: 'upemail',
fired_at: '2025-01-24 19:30:00',
data: {
list_id: 'a6b5da1054',
new_id: '9b36fa2e09', // New subscriber ID (MD5 of new lowercase email)
new_email: 'newemail@example.com',
old_email: 'oldemail@example.com'
}
}
Key Fields:
data.old_email- Previous email addressdata.new_email- Updated email addressdata.new_id- New subscriber ID (recalculated based on new email)- Note: No
data.idfield in this event (usenew_id)
Use Cases:
- Update email address across all connected systems
- Maintain user account integrity (same user, different email)
- Track email change frequency for security monitoring
- Update authentication systems if email is used for login
- Migrate historical data to new email identifier
Important Notes:
- This is a RARE event (most users don't change emails via Mailchimp)
- The old subscriber record is deleted and new one created (different ID)
- Update both email and subscriber ID in your systems
- Historical campaign stats remain associated with old email
Description: Fires when a campaign (email) is sent to your audience. Provides campaign metadata but not individual recipient data.
Payload Structure:
{
type: 'campaign',
fired_at: '2025-01-24 20:00:00',
data: {
id: 'c123456789', // Campaign ID
subject: 'January Newsletter', // Email subject line
status: 'sent', // Campaign status: 'sent', 'sending', 'paused', 'canceled'
list_id: 'a6b5da1054',
send_time: '2025-01-24 20:00:00'
}
}
Key Fields:
data.id- Campaign ID (use to fetch detailed stats via Reports API)data.subject- Email subject linedata.status- Campaign status (usually 'sent' or 'sending')
Use Cases:
- Log campaign send times in analytics dashboard
- Trigger post-send workflows (e.g., check engagement after 1 hour)
- Notify team in Slack when campaign goes out
- Track campaign frequency and timing patterns
- Correlate campaign sends with website traffic spikes
Important Notes:
- This webhook does NOT contain individual recipient data (no emails, no open/click tracking)
- For email engagement tracking (opens/clicks), use Mailchimp Transactional (Mandrill) webhooks
- For detailed campaign reports, use Mailchimp Reports API after campaign completes
- Campaign webhooks fire when sending starts (not when complete for large lists)