Agent skill

Jamie Webhooks Skill

Receive and verify Jamie (meetjamie.ai) AI meeting notes webhooks. Use when setting up Jamie webhook handlers, debugging x-jamie-signature verification, or handling the meeting.completed event with its summary, transcript and tasks.

Install this skill

npx skills add hookdeck/webhook-skills --skill jamie-webhooks


When to Use This Skill

  • How do I receive Jamie webhooks?
  • How do I verify the x-jamie-signature header?
  • How do I handle the meeting.completed event (summary, transcript, tasks)?
  • Why is my Jamie webhook signature verification failing?
  • Which Jamie authentication method works with Hookdeck?

Choose Signature Verification

Choose Signature Verification when creating the Jamie webhook and paste its signing secret. API key mode is not supported.

Jamie offers two modes at webhook creation: API Key (its recommended default, a static key in a header of your choice, x-jamie-api-key by default) and Signature Verification. Hookdeck's Jamie source (JAMIE) verifies the signature only, so this skill covers that mode.

Essential Code (USE THIS)

Jamie sends x-jamie-signature: t=<unix seconds>,v0=<lowercase hex>. v0 is HMAC-SHA256 of "<t>.<raw body>", keyed with the signing secret string's UTF-8 bytes as-is (not decoded). Use the raw body bytes, not re-serialised JSON. Jamie has no verification SDK, so verify manually.

Verification (Node.js)

const crypto = require('crypto');

// rawBody: Buffer of the exact request bytes (express.raw({ type: '*/*' }), not express.json)
function verifyJamie(rawBody, signatureHeader, secret, toleranceSeconds = 0) {
  if (!secret || !signatureHeader) return false;
  let t; const v0 = [];
  for (const kv of signatureHeader.split(',')) {
    const i = kv.indexOf('=');
    if (i <= 0) continue;
    const key = kv.slice(0, i).trim(), value = kv.slice(i + 1).trim();
    if (key === 't') t = value; else if (key === 'v0') v0.push(value);
  }
  if (!t || !/^\d+$/.test(t) || v0.length === 0) return false;
  if (toleranceSeconds > 0 && Math.abs(Date.now() / 1000 - Number(t)) > toleranceSeconds) return false;
  const expected = crypto.createHmac('sha256', secret).update(`${t}.`).update(rawBody).digest('hex');
  return v0.some((sig) => sig.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)));
}

Verification (Python)

import hashlib, hmac, time

def verify_jamie(raw_body: bytes, header: str, secret: str, tolerance: int = 0) -> bool:
    if not secret or not header:
        return False
    t, v0 = None, []
    for kv in header.split(","):
        key, sep, value = kv.partition("=")
        if sep and key.strip() == "t": t = value.strip()
        elif sep and key.strip() == "v0": v0.append(value.strip())
    if not t or not t.isdigit() or not v0:
        return False
    if tolerance > 0 and abs(time.time() - int(t)) > tolerance:
        return False
    expected = hmac.new(secret.encode(), t.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    return any(hmac.compare_digest(sig.encode(), expected.encode()) for sig in v0)

Respond 401 when the header is missing or malformed or the signature does not match.

Timestamp window

The timestamp window is opt-in: JAMIE_TIMESTAMP_TOLERANCE_SECONDS defaults to 0 (off). Jamie's docs recommend rejecting timestamps older than 5 minutes (300 s) when Jamie calls you directly, so set 300 there if you want that. Behind Hookdeck keep 0: Hookdeck retries and replays resend the original timestamp (and Jamie's own retries run up to about an hour after the first attempt), so a window rejects late redeliveries.

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Common Event Types

The event name is in metadata.event and in the jamie-event header.

EventDescription
meeting.completedA meeting is fully processed: summary (Markdown/HTML/short), transcript, tasks, participants, calendar event and tags

meeting.completed is currently the only event. Jamie's metadata.id (also the jamie-delivery header) identifies the delivery, so dedupe on it. See references/overview.md.

For the full payload reference, see Jamie webhooks getting started.

Important Headers

HeaderDescription
x-jamie-signaturet=<unix seconds>,v0=<lowercase hex HMAC-SHA256> (Signature Verification mode)
jamie-eventEvent type, e.g. meeting.completed
jamie-deliveryUnique ID for this delivery

Environment Variables

JAMIE_WEBHOOK_SECRET=your_signing_secret   # shown once when the webhook is created
JAMIE_TIMESTAMP_TOLERANCE_SECONDS=0        # opt-in replay window; Jamie's docs suggest 300 when called directly

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 jamie --path /webhooks/jamie

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 9, 2026

View on GitHub →