Skip to main content
Follow these security best practices when integrating with the meetergo API.

API Key Security

Storage

Use environment variables - Never hardcode API keys
# .env (never commit this file)
MEETERGO_API_KEY=ak_live:uuid:secret
// Access via environment
const apiKey = process.env.MEETERGO_API_KEY;
Use secrets managers for production deployments
  • AWS Secrets Manager
  • Google Secret Manager
  • HashiCorp Vault
  • Azure Key Vault
Never commit API keys to version control
Add to .gitignore:
.env
.env.local
.env.*.local

Key Rotation

API keys expire after 1-90 days. Implement rotation:
  1. Create new key before the old one expires
  2. Deploy with the new key
  3. Old key automatically expires
// Track key expiration
const keyCreatedAt = new Date('2024-01-15');
const expiresAt = new Date(keyCreatedAt);
expiresAt.setDate(expiresAt.getDate() + 90);

const daysUntilExpiry = Math.floor((expiresAt - Date.now()) / (1000 * 60 * 60 * 24));

if (daysUntilExpiry < 7) {
  console.warn(`API key expires in ${daysUntilExpiry} days!`);
}

Key Isolation

Use separate API keys for:
EnvironmentKey Name
DevelopmentDevelopment
StagingStaging
ProductionProduction
This limits blast radius if a key is compromised.

Webhook Security

Use HTTPS

Always use HTTPS endpoints for webhooks:
// Good
const endpoint = 'https://your-server.com/webhooks/meetergo';

// Bad - never use HTTP in production
const endpoint = 'http://your-server.com/webhooks/meetergo';

Validate Payloads

Verify webhook payloads before processing:
app.post('/webhooks/meetergo', (req, res) => {
  const { event, data } = req.body;

  // Validate required fields
  if (!event || !data) {
    console.error('Invalid webhook payload');
    return res.status(400).send('Invalid payload');
  }

  // Validate event type
  const validEvents = ['booking_created', 'booking_cancelled', 'booking_rescheduled', 'new_employee'];
  if (!validEvents.includes(event)) {
    console.error('Unknown event type:', event);
    return res.status(400).send('Unknown event');
  }

  // Process the webhook
  res.status(200).send('OK');
});

Prevent Replay Attacks

Use timestamps and idempotency:
const processedEvents = new Map();
const MAX_AGE_MS = 5 * 60 * 1000; // 5 minutes

app.post('/webhooks/meetergo', (req, res) => {
  const { event, data } = req.body;
  const eventKey = `${event}:${data.id}:${data.updatedAt}`;

  // Check for replays
  if (processedEvents.has(eventKey)) {
    console.log('Duplicate event, skipping');
    return res.status(200).send('OK');
  }

  // Check timestamp (if available)
  const eventTime = new Date(data.updatedAt || data.createdAt);
  if (Date.now() - eventTime.getTime() > MAX_AGE_MS) {
    console.warn('Old event received, possible replay');
  }

  processedEvents.set(eventKey, Date.now());
  res.status(200).send('OK');

  // Clean up old entries periodically
  for (const [key, timestamp] of processedEvents) {
    if (Date.now() - timestamp > MAX_AGE_MS) {
      processedEvents.delete(key);
    }
  }
});

Data Handling

Minimize Data Exposure

Only request and store data you need:
// Store only necessary fields
function storeBooking(webhookData) {
  return {
    id: webhookData.id,
    start: webhookData.start,
    end: webhookData.end,
    attendeeEmail: webhookData.attendees[0]?.email,
    // Don't store sensitive fields you don't need
  };
}

Sanitize User Input

Validate and sanitize data before using:
function sanitizeAttendee(attendee) {
  return {
    email: validateEmail(attendee.email),
    firstname: sanitizeString(attendee.firstname, 100),
    lastname: sanitizeString(attendee.lastname, 100),
    phone: sanitizePhone(attendee.phone),
  };
}

function validateEmail(email) {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!email || !emailRegex.test(email)) {
    throw new Error('Invalid email');
  }
  return email.toLowerCase().trim();
}

function sanitizeString(str, maxLength) {
  if (!str) return '';
  return str.slice(0, maxLength).trim();
}

Protect PII

Handle personally identifiable information carefully:
  • Encrypt at rest
  • Encrypt in transit (HTTPS)
  • Implement access controls
  • Log access to sensitive data
  • Follow data retention policies

Logging

Do Log

  • Request timestamps
  • Response status codes
  • Error messages (without sensitive data)
  • Rate limit warnings

Don’t Log

  • API keys
  • Full request bodies with PII
  • Webhook payloads with customer data
// Good logging
console.log({
  timestamp: new Date().toISOString(),
  endpoint: '/v4/booking',
  method: 'POST',
  statusCode: 201,
  durationMs: 150
});

// Bad - exposes API key
console.log({
  headers: request.headers // Contains Authorization header!
});

// Bad - exposes customer data
console.log({
  body: request.body // Contains email, phone, etc.
});

Server Security

Keep Dependencies Updated

Regularly update your dependencies:
# Check for vulnerabilities
npm audit

# Update packages
npm update

Use Security Headers

Protect your webhook endpoints:
const helmet = require('helmet');
app.use(helmet());

// Or manually:
app.use((req, res, next) => {
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('X-Frame-Options', 'DENY');
  res.setHeader('X-XSS-Protection', '1; mode=block');
  next();
});

Rate Limit Your Endpoints

Protect against abuse:
const rateLimit = require('express-rate-limit');

const webhookLimiter = rateLimit({
  windowMs: 60 * 1000, // 1 minute
  max: 100, // limit each IP
  message: 'Too many requests'
});

app.use('/webhooks', webhookLimiter);

Incident Response

If you suspect a key is compromised:
  1. Immediately revoke the key in the dashboard
  2. Create a new key and update your application
  3. Review logs for unauthorized access
  4. Audit what data may have been accessed
  5. Notify affected users if required

Security Checklist

API keys stored in environment variables or secrets manager
API keys never committed to version control
Separate keys for development and production
Key rotation before expiration
HTTPS for all webhook endpoints
Webhook payload validation
No sensitive data in logs
Dependencies regularly updated
Security headers configured
Rate limiting on your endpoints