Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.meetjamie.ai/llms.txt

Use this file to discover all available pages before exploring further.

Jamie offers two authentication methods for webhooks. Choose the one that best fits your needs.

Authentication Methods

The simplest way to verify webhook requests. Jamie sends a static API key in a header that you specify during webhook creation. How it works:
  1. When creating the webhook, choose “API Key” authentication
  2. Optionally specify a custom header name (default: x-jamie-api-key)
  3. Save the generated secret key (sk_...)
  4. In your webhook handler, compare the header value with your stored secret

Verification Code Examples

const express = require('express');
const app = express();

app.use(express.json());

app.post('/webhooks/jamie', (req, res) => {
  // Get the API key from header (use your custom header name if configured)
  const apiKey = req.headers['x-jamie-api-key'];
  
  // Compare with your stored secret
  if (apiKey !== process.env.JAMIE_WEBHOOK_SECRET) {
    console.error('Invalid API key');
    return res.status(401).send('Unauthorized');
  }
  
  // API key is valid, process the webhook
  const { data } = req.body;
  console.log('Meeting completed:', data.event.title);
  console.log('Summary:', data.summary.markdown);
  
  // Process tasks
  data.tasks.forEach(task => {
    console.log('Task:', task.content);
  });
  
  res.status(200).send('OK');
});

app.listen(3000, () => {
  console.log('Webhook server running on port 3000');
});

Signature Verification (Advanced)

For maximum security, Jamie can sign all webhook requests using HMAC-SHA256. This ensures requests are authentic and haven’t been tampered with.

Signature Format

The x-jamie-signature header uses the following format:
t=<timestamp>,v0=<signature>
  • t: Unix timestamp (seconds) when the signature was created
  • v0: HMAC-SHA256 signature of the payload

Verification Steps

  1. Extract the timestamp and signature from the x-jamie-signature header
  2. Validate the timestamp - Reject requests with timestamps older than 5 minutes (300 seconds) to prevent replay attacks
  3. Recreate the signed message - Concatenate the timestamp, a period (.), and the raw request body: {timestamp}.{raw_body}
  4. Compute the expected signature - Generate an HMAC-SHA256 hash of the message using your signing secret
  5. Compare signatures - Use a constant-time comparison to prevent timing attacks

Verification Code Examples

const crypto = require('crypto');

async function verifyWebhookSignature(
  payload: string,
  signature: string,
  secret: string
): Promise<boolean> {
  // Parse signature header: t=timestamp,v0=hash
  const parts = signature.split(',');
  const timestamp = parts[0].split('=')[1];
  const expectedSignature = parts[1].split('=')[1];

  // Validate timestamp (prevent replay attacks - 5 min tolerance)
  const now = Math.floor(Date.now() / 1000);
  if (now - parseInt(timestamp) > 300) {
    throw new Error('Signature expired');
  }

  // Recreate the signed message
  const message = `${timestamp}.${payload}`;

  // Generate HMAC signature
  const hmac = crypto.createHmac('sha256', secret);
  hmac.update(message);
  const actualSignature = hmac.digest('hex');

  // Compare signatures using constant-time comparison
  const expectedBuffer = Buffer.from(expectedSignature, 'hex');
  const actualBuffer = Buffer.from(actualSignature, 'hex');
  
  if (!crypto.timingSafeEqual(expectedBuffer, actualBuffer)) {
    throw new Error('Invalid signature');
  }

  return true;
}

// Usage in your webhook endpoint (Express example):
app.post('/webhooks/jamie', async (req, res) => {
  const signature = req.headers['x-jamie-signature'];
  const payload = JSON.stringify(req.body);
  
  try {
    await verifyWebhookSignature(
      payload,
      signature,
      process.env.JAMIE_WEBHOOK_SECRET
    );
    
    // Signature is valid, process the webhook
    const { data } = req.body;
    console.log('Meeting completed:', data.event.title);
    
    res.status(200).send('OK');
  } catch (error) {
    console.error('Webhook verification failed:', error);
    res.status(401).send('Unauthorized');
  }
});
Security Best Practices:
  • Always verify the API key or signature before processing webhook data
  • For signature verification, use constant-time comparison to prevent timing attacks
  • Validate timestamps to prevent replay attacks (recommended tolerance: 5 minutes)
  • Store your secret key securely (use environment variables or a secrets manager)
  • Never commit secret keys to version control