Sign in

Webhooks

Send your bookings to Zapier, Make, n8n or your own code.

Tempi sends each new, requested, moved or cancelled booking to an address you choose, as signed JSON. Put bookings in your CRM, a spreadsheet or a chat without waiting for an integration. Free.

Get your free linkOpen TeamsSign in with Google, Microsoft or email. No card needed.

Add a webhook

Open Teams in Tempi and pick the team, or your personal one. Under Webhooks, paste the address that should get the bookings and choose Add webhook. In Zapier that’s the Catch Hook URL of a Webhooks by Zapier trigger. In Make it’s a Custom webhook. In n8n it’s a Webhook node.

Copy the signing secret that appears. It’s shown once. Then choose Send test: Tempi sends a ping and shows what your address answered.

Only team admins see and change a team’s webhooks. A team can have 5. AI assistants connected to your account can add them too, with create_webhook.

What Tempi sends

A POST with a JSON body for each change to one of the team’s bookings. type is booking.created (booked, or a request approved), booking.requested (waiting for approval), booking.rescheduled or booking.cancelled (cancelled, declined, or a request nobody answered). booking has its id, status, start and end in UTC, the event type, the team, the invitee with their time zone, the hosts with the organizer marked, where it happens and the invitee’s notes.

A move says where it was in change.previousStart. A cancellation says who cancelled it and why in change.by and change.reason. When cancelling one meeting of a series cancels the later ones too, change.alsoCancelled lists them. A new series is sent once, as its first meeting, with its seriesId.

booking.rescheduled

{
  "id": "whe_…",
  "type": "booking.rescheduled",
  "createdAt": "2026-10-07T09:00:05.000Z",
  "booking": {
    "id": "bkg_…",
    "status": "confirmed",
    "title": "Intro call: Sam Lee × Maya",
    "start": "2026-10-13T16:00:00.000Z",
    "end": "2026-10-13T16:30:00.000Z",
    "eventType": { "id": "evt_…", "slug": "intro", "title": "Intro call" },
    "team": { "id": "team_…", "slug": "acme", "name": "Acme" },
    "invitee": { "name": "Sam Lee", "email": "sam@example.com", "timezone": "America/New_York", "phone": null },
    "hosts": [{ "id": "usr_…", "name": "Maya", "email": "maya@acme.com", "organizer": true }],
    "where": { "kind": "video", "meetingUrl": "https://meet.google.com/…", "address": null },
    "notes": "About the pilot",
    "seriesId": null
  },
  "change": { "by": "invitee", "previousStart": "2026-10-12T16:00:00.000Z", "previousEnd": "2026-10-12T16:30:00.000Z" }
}

Check that it came from Tempi

Each request has a Tempi-Signature header like t=1760000000,v1=5f3c…. Compute the HMAC-SHA256 of the t value, a dot and the raw body, with your signing secret, in hex. It must equal v1. Refuse requests whose t is more than five minutes old, so an old request can’t be sent again.

Tempi-Event names the event type and Tempi-Delivery the event’s id. The same change is sent with the same id, so skip an id you’ve already handled.

Node.js

import { createHmac, timingSafeEqual } from 'node:crypto'

function fromTempi(rawBody, header, secret) {
  const { t, v1 } = Object.fromEntries(header.split(',').map((kv) => kv.split('=')))
  const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex')
  const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300
  return fresh && v1?.length === expected.length && timingSafeEqual(Buffer.from(v1), Buffer.from(expected))
}

When your address doesn’t answer

Answer with any 2xx status within 10 seconds. If your address can’t be reached or answers 5xx, Tempi tries once more right away. The booking goes ahead either way.

The team page shows when each webhook last sent and what it answered. After 20 failures in a row, Tempi turns the webhook off and says so. Fix your address and choose Send test to turn it back on.

Addresses must start with https://.

More