Authentication
Secure your API calls and webhook endpoints with proper authentication methods including API tokens and HMAC signatures.
Prerequisites
- MERU account and API token
Authentication
MERU uses multiple authentication methods to ensure your data remains secure. This guide covers API token authentication for making requests and HMAC signature verification for webhook security.
API Token Authentication
Getting Your API Token
- Login to your MERU dashboard
- Navigate to Settings > API Tokens
- Click Create Token and provide a descriptive name
- Copy and store your token securely
Security Note: API tokens provide full account access. Store them securely and never commit them to version control.
Using API Tokens
Include your API token in the Authorization
header for all API requests:
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
https://api.meruhook.com/v1/addresses
Token Formats
API tokens follow this format:
meru_live_abc123def456... # Production tokens
meru_test_abc123def456... # Test/sandbox tokens
Best Practices
✅ Do:
- Store tokens as environment variables
- Use different tokens for different environments
- Rotate tokens regularly
- Restrict token permissions when possible
❌ Don’t:
- Commit tokens to version control
- Share tokens in chat/email
- Use production tokens in development
- Log tokens in application logs
Environment Variables
Store your tokens securely:
# .env file
MERU_API_TOKEN=meru_live_abc123def456...
MERU_TEST_TOKEN=meru_test_abc123def456...
Choose your language
// JavaScript
const token = process.env.MERU_API_TOKEN;
// Make authenticated request
const response = await fetch('https://api.meruhook.com/v1/addresses', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
Webhook Authentication
Webhooks use HMAC-SHA256 signatures to verify that requests actually come from MERU and haven’t been tampered with.
Webhook Secret
Each inbound address gets a unique webhook secret. Find it in your dashboard or via API:
curl -H "Authorization: Bearer YOUR_API_TOKEN" \
https://api.meruhook.com/v1/addresses/addr_abc123
Response includes the webhook secret:
{
"id": "addr_abc123",
"address": "user123@inbound.meruhook.com",
"webhook_url": "https://your-app.com/webhook",
"webhook_secret": "whsec_abc123def456...",
...
}
Signature Verification
MERU includes the signature in the Meru-Signature
header:
Meru-Signature: v1,t=1693123456,s=abc123def456...
The header format:
v1
: Signature versiont
: Unix timestamps
: HMAC-SHA256 signature
Verification Examples
Node.js / Express
const crypto = require('crypto');
function verifyWebhook(payload, signature, secret) {
const elements = signature.split(',');
const timestamp = elements.find(el => el.startsWith('t=')).slice(2);
const sig = elements.find(el => el.startsWith('s=')).slice(2);
// Create signed payload
const signedPayload = timestamp + '.' + payload;
// Calculate expected signature
const expectedSig = crypto
.createHmac('sha256', secret)
.update(signedPayload, 'utf8')
.digest('hex');
// Compare signatures (timing-safe)
return crypto.timingSafeEqual(
Buffer.from(sig, 'hex'),
Buffer.from(expectedSig, 'hex')
);
}
// Usage in Express
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
const signature = req.headers['meru-signature'];
const payload = req.body.toString();
if (!verifyWebhook(payload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Process verified webhook
const email = JSON.parse(payload);
console.log('Verified email:', email.headers.subject);
res.status(200).send('OK');
});
Python / Flask
import hashlib
import hmac
import time
def verify_webhook(payload, signature, secret):
elements = signature.split(',')
timestamp = next(el[2:] for el in elements if el.startswith('t='))
sig = next(el[2:] for el in elements if el.startswith('s='))
# Create signed payload
signed_payload = f"{timestamp}.{payload}"
# Calculate expected signature
expected_sig = hmac.new(
secret.encode('utf-8'),
signed_payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
# Compare signatures (timing-safe)
return hmac.compare_digest(sig, expected_sig)
# Usage in Flask
@app.route('/webhook', methods=['POST'])
def handle_webhook():
signature = request.headers.get('Meru-Signature')
payload = request.get_data(as_text=True)
if not verify_webhook(payload, signature, os.getenv('WEBHOOK_SECRET')):
return 'Invalid signature', 401
# Process verified webhook
email = request.json
print(f"Verified email: {email['headers']['subject']}")
return 'OK', 200
PHP / Laravel
function verifyWebhook($payload, $signature, $secret) {
$elements = explode(',', $signature);
$timestamp = null;
$sig = null;
foreach ($elements as $element) {
if (strpos($element, 't=') === 0) {
$timestamp = substr($element, 2);
} elseif (strpos($element, 's=') === 0) {
$sig = substr($element, 2);
}
}
// Create signed payload
$signedPayload = $timestamp . '.' . $payload;
// Calculate expected signature
$expectedSig = hash_hmac('sha256', $signedPayload, $secret);
// Compare signatures (timing-safe)
return hash_equals($sig, $expectedSig);
}
// Usage in Laravel
Route::post('/webhook', function (Request $request) {
$signature = $request->header('Meru-Signature');
$payload = $request->getContent();
if (!verifyWebhook($payload, $signature, env('WEBHOOK_SECRET'))) {
return response('Invalid signature', 401);
}
// Process verified webhook
$email = $request->json()->all();
Log::info('Verified email: ' . $email['headers']['subject']);
return response('OK');
});
Timestamp Verification
To prevent replay attacks, verify the timestamp is recent:
function verifyTimestamp(signature, tolerance = 300) { // 5 minutes
const elements = signature.split(',');
const timestamp = parseInt(elements.find(el => el.startsWith('t=')).slice(2));
const now = Math.floor(Date.now() / 1000);
return Math.abs(now - timestamp) <= tolerance;
}
Security Best Practices
API Security
- Use HTTPS only for all API requests
- Validate responses - Check status codes and response structure
- Handle rate limits - Implement exponential backoff
- Log security events - Monitor for unusual API usage
Webhook Security
- Always verify signatures - Never skip signature verification
- Use HTTPS endpoints - Webhooks require SSL/TLS
- Implement idempotency - Handle duplicate webhook deliveries
- Validate timestamp - Prevent replay attacks
Token Management
- Rotate regularly - Update tokens every 90 days
- Monitor usage - Check API logs for suspicious activity
- Separate environments - Use different tokens for dev/staging/prod
- Revoke compromised tokens - Immediately revoke and replace
Troubleshooting
Common Authentication Errors
401 Unauthorized
{
"error": "unauthorized",
"message": "Invalid or missing API token"
}
Solution: Check your Authorization
header format and token validity.
403 Forbidden
{
"error": "forbidden",
"message": "Token does not have required permissions"
}
Solution: Verify your token has the necessary permissions for the requested operation.
Invalid Webhook Signature
Symptoms: Webhook requests failing verification Solutions:
- Ensure you’re using the correct webhook secret
- Verify timestamp is included in signature calculation
- Check that payload matches exactly (including whitespace)
- Confirm HMAC algorithm is SHA-256
Testing Authentication
Test API Authentication
# Test with valid token
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://api.meruhook.com/v1/addresses
# Should return 200 OK with address list
Test Webhook Signature
Use tools like webhook.site to inspect webhook deliveries and verify signatures.
Next Steps: Learn about webhook configuration and API endpoints to build robust inbound email processing.
Related Documentation
Last updated: September 5, 2025