Overview
Webhooks allow you to receive real-time HTTP notifications when events occur in your Migma account. Instead of constantly checking for updates, Migma will automatically send instant POST requests to your specified endpoint whenever something important happens.
Important: Webhooks are triggered only for events that occur through the API v1 endpoints (when using API keys). Events from the Migma web dashboard do not trigger webhooks. This design ensures webhooks are used for API integrations and automations.
Getting Started
1. Create Your Webhook Endpoint
Set up an HTTPS endpoint on your server to receive webhook events:
const express = require('express');
const crypto = require('crypto');
app.post('/webhooks/migma', express.json(), (req, res) => {
// Verify signature (see security section below)
const signature = req.headers['x-migma-signature'];
const isValid = verifySignature(req.body, signature, process.env.MIGMA_WEBHOOK_SECRET);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
// Process the event
const event = req.body;
console.log(`Received ${event.type}:`, event.data);
// Return 200 to acknowledge receipt
res.sendStatus(200);
});
from flask import Flask, request
import hmac
import hashlib
import json
@app.route('/webhooks/migma', methods=['POST'])
def webhook():
# Verify signature
signature = request.headers.get('X-Migma-Signature')
payload = request.get_json()
if not verify_signature(payload, signature, os.getenv('MIGMA_WEBHOOK_SECRET')):
return 'Invalid signature', 401
# Process the event
event_type = payload['type']
print(f"Received {event_type}: {payload['data']}")
# Return 200 to acknowledge receipt
return '', 200
2. Register Your Webhook in Migma Dashboard
- Log in to your Migma dashboard
- Navigate to Settings → API Integration → Webhooks tab
- Click Create Webhook
- Enter your endpoint URL (must be HTTPS)
- Select the events you want to receive
- Click Save
- Copy your webhook secret (shown only once!)
3. Verify It Works
Use the test button in your dashboard to send a test event to your endpoint. Check that your server receives the request and responds with 200 OK.
Available Events
Migma sends webhooks for the following events:
Email Generation
| Event | When It Fires |
|---|
email.generation.started | Email generation begins via API |
email.generation.completed | Email successfully generated |
email.generation.failed | Email generation encountered an error |
Project Import
| Event | When It Fires |
|---|
project.import.started | Brand import begins via API |
project.import.processing | Brand is being processed |
project.import.completed | Brand import finished successfully |
project.import.failed | Brand import encountered an error |
Exports
| Event | When It Fires |
|---|
export.completed | Email export to Mailchimp/HubSpot/Klaviyo completed |
export.failed | Email export encountered an error |
Subscribers
| Event | When It Fires |
|---|
subscriber.added | New subscriber added via API |
subscriber.updated | Subscriber information updated via API |
subscriber.unsubscribed | Subscriber unsubscribed |
subscriber.bulk_imported | Bulk import of subscribers completed |
API Keys
| Event | When It Fires |
|---|
api_key.created | New API key created via dashboard |
api_key.revoked | API key revoked via dashboard |
Webhook Payload Structure
All webhook events follow this consistent structure:
{
"id": "evt_1234567890abcdef",
"type": "email.generation.completed",
"timestamp": "2025-01-30T12:34:56.789Z",
"data": {
// Event-specific data
},
"metadata": {
"projectId": "proj_abc123",
"conversationId": "conv_xyz789"
}
}
Field Descriptions
- id: Unique event identifier (use for idempotency)
- type: Event type from the list above
- timestamp: ISO 8601 timestamp when the event occurred
- data: Event-specific payload (see examples below)
- metadata: Additional context (project ID, conversation ID, etc.)
Example Payloads
Email Generation Completed
{
"id": "evt_a1b2c3d4e5f6",
"type": "email.generation.completed",
"timestamp": "2025-01-30T12:34:56.789Z",
"data": {
"conversationId": "conv_xyz789",
"projectId": "proj_abc123",
"subject": "Welcome to Our Newsletter",
"generationTime": 3245,
"wordCount": 428
},
"metadata": {
"projectId": "proj_abc123",
"conversationId": "conv_xyz789"
}
}
Project Import Completed
{
"id": "evt_b2c3d4e5f6g7",
"type": "project.import.completed",
"timestamp": "2025-01-30T12:35:00.123Z",
"data": {
"projectId": "proj_abc123",
"name": "My Brand",
"url": "https://mybrand.com",
"importTime": 45230
},
"metadata": {
"projectId": "proj_abc123"
}
}
Export Completed
{
"id": "evt_c3d4e5f6g7h8",
"type": "export.completed",
"timestamp": "2025-01-30T12:36:15.456Z",
"data": {
"conversationId": "conv_xyz789",
"provider": "mailchimp",
"campaignId": "mc_campaign_123",
"exportTime": 2340
},
"metadata": {
"conversationId": "conv_xyz789"
}
}
Subscriber Added
{
"id": "evt_d4e5f6g7h8i9",
"type": "subscriber.added",
"timestamp": "2025-01-30T12:37:00.789Z",
"data": {
"email": "[email protected]",
"projectId": "proj_abc123",
"tags": ["newsletter", "customer"]
},
"metadata": {
"projectId": "proj_abc123"
}
}
Security
Verifying Webhook Signatures
Always verify the X-Migma-Signature header to ensure requests came from Migma and haven’t been tampered with.
Node.js Implementation
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const computedSignature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(computedSignature)
);
}
app.post('/webhooks/migma', express.json(), (req, res) => {
const signature = req.headers['x-migma-signature'];
const isValid = verifyWebhookSignature(
req.body,
signature,
process.env.MIGMA_WEBHOOK_SECRET
);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
// Process the webhook
res.sendStatus(200);
});
Python Implementation
import hmac
import hashlib
import json
def verify_webhook_signature(payload, signature, secret):
computed_signature = hmac.new(
secret.encode(),
json.dumps(payload, separators=(',', ':')).encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, computed_signature)
@app.route('/webhooks/migma', methods=['POST'])
def webhook():
signature = request.headers.get('X-Migma-Signature')
payload = request.get_json()
if not verify_webhook_signature(payload, signature, os.getenv('MIGMA_WEBHOOK_SECRET')):
return 'Invalid signature', 401
# Process the webhook
return '', 200
Security Best Practices
- Always verify signatures - Never process webhooks without verification
- Use HTTPS only - Migma only sends webhooks to HTTPS endpoints
- Store secrets securely - Use environment variables, never hardcode
- Implement timeouts - Don’t process webhooks older than 5 minutes
- Rate limit - Protect against potential abuse
Delivery & Reliability
Non-Blocking Delivery
Webhook triggers are completely non-blocking:
- ✅ API responses return instantly, regardless of webhook configuration
- ✅ Webhook delivery happens in the background after your API call succeeds
- ✅ Webhook failures never affect your API request success
- ✅ Multiple webhooks are delivered in parallel
What this means: Your API calls are never delayed by webhook delivery, even if your webhook endpoint is slow or down.
Automatic Retries
Migma automatically retries failed webhook deliveries with exponential backoff:
- 1st retry: After 1 minute
- 2nd retry: After 5 minutes
- 3rd retry: After 15 minutes
Return 200-299 status codes to indicate success. Any other status code (or timeout) triggers a retry.
Delivery Guarantees
- At-least-once delivery: Events are delivered at least once (possibly more during retries)
- Idempotency: Use the event
id to prevent duplicate processing
- Persistent queue: Delivery attempts are stored for reliability
- 10-second timeout: Each delivery attempt times out after 10 seconds
Best Practices
Respond Quickly
Acknowledge receipt immediately, then process asynchronously:
app.post('/webhooks/migma', (req, res) => {
// Acknowledge receipt immediately
res.sendStatus(200);
// Process asynchronously
processWebhookAsync(req.body).catch(err => {
console.error('Webhook processing error:', err);
});
});
Implement Idempotency
Use the event id to prevent duplicate processing:
const processedEvents = new Set();
app.post('/webhooks/migma', async (req, res) => {
const eventId = req.body.id;
if (processedEvents.has(eventId)) {
return res.sendStatus(200); // Already processed
}
// Process event
await handleEvent(req.body);
processedEvents.add(eventId);
res.sendStatus(200);
});
Handle Retries Gracefully
app.post('/webhooks/migma', async (req, res) => {
try {
// Verify signature
if (!verifySignature(req)) {
return res.status(401).send('Invalid signature');
}
// Process event (idempotent)
await handleEvent(req.body);
res.sendStatus(200);
} catch (error) {
console.error('Webhook error:', error);
// Return 500 to trigger retry
res.status(500).send('Processing failed');
}
});
Route by Event Type
app.post('/webhooks/migma', async (req, res) => {
const { type, data } = req.body;
switch (type) {
case 'email.generation.completed':
await handleEmailCompleted(data);
break;
case 'email.generation.failed':
await handleEmailFailed(data);
break;
case 'export.completed':
await handleExportCompleted(data);
break;
default:
console.log(`Unknown event type: ${type}`);
}
res.sendStatus(200);
});
Testing
Local Development
Use ngrok to expose your local server:
Then register the ngrok URL (e.g., https://abc123.ngrok.io/webhooks/migma) in your Migma dashboard.
Test Events
Click the test button (🔄) next to your webhook in the dashboard to send a test payload:
{
"id": "evt_test_123",
"type": "email.generation.completed",
"timestamp": "2025-01-30T12:00:00.000Z",
"data": {
"conversationId": "test_conv",
"projectId": "test_proj",
"subject": "Test Email"
},
"metadata": {
"test": true
}
}
Debug Mode
Enable verbose logging to troubleshoot:
app.post('/webhooks/migma', (req, res) => {
console.log('Headers:', req.headers);
console.log('Body:', JSON.stringify(req.body, null, 2));
console.log('Signature:', req.headers['x-migma-signature']);
// ... rest of your code
});
Monitoring
Dashboard Monitoring
Monitor webhook health in your Migma dashboard:
- Go to Settings → API Integration → Webhooks
- View success/failure counts for each webhook
- Click the clock icon to see delivery history
- Check response codes and timing for each delivery
Metrics to Track
- Success rate: Percentage of successful deliveries
- Response time: How long your endpoint takes to respond
- Failure patterns: Common error codes or failure reasons
- Retry frequency: How often retries are needed
Troubleshooting
Common Issues
| Issue | Solution |
|---|
| Signature verification fails | Ensure you’re using the correct secret and computing HMAC over the raw JSON body |
| Timeouts | Respond with 200 immediately, process asynchronously |
| Duplicate events | Implement idempotency using event IDs |
| Missing events | Check your endpoint returns 200-299 status codes |
| Webhook not firing | Verify the event is triggered via API (not dashboard) |
HTTP Response Codes
Return these codes to control webhook behavior:
- 200-299: Success - no retry
- 400-499: Client error - no retry (except 408, 429)
- 500-599: Server error - will retry
- Timeout (10s): Will retry
Rate Limits
Your webhook endpoint should handle:
- Up to 100 requests per second during high activity
- Burst traffic from batch operations (e.g., bulk imports)
Ensure your endpoint can scale appropriately.
Example Use Cases
Automated Email Workflows
// Trigger follow-up actions when email generation completes
case 'email.generation.completed':
const { conversationId, projectId, subject } = data;
// Log to your analytics
await analytics.track('email_generated', {
projectId,
conversationId,
subject
});
// Notify your team
await slack.notify({
channel: '#marketing',
message: `New email generated: "${subject}"`
});
break;
Error Monitoring
// Alert your team when generation fails
case 'email.generation.failed':
await slack.notify({
channel: '#alerts',
message: `Email generation failed for project ${data.projectId}`,
error: data.error
});
break;
Integration Sync
// Sync subscribers to your CRM
case 'subscriber.added':
await crm.createContact({
email: data.email,
source: 'migma',
tags: data.tags
});
break;
Need Help?
Performance: Webhooks are completely non-blocking and never impact API response times.
Reliability: Automatic retries with exponential backoff ensure delivery even during temporary outages.
Security: HMAC SHA-256 signature verification protects against unauthorized requests.