Authentication

MERU Documentation - Security

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

  1. Login to your MERU dashboard
  2. Navigate to Settings > API Tokens
  3. Click Create Token and provide a descriptive name
  4. 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 version
  • t: Unix timestamp
  • s: 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:

  1. Ensure you’re using the correct webhook secret
  2. Verify timestamp is included in signature calculation
  3. Check that payload matches exactly (including whitespace)
  4. 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.

Last updated: September 5, 2025