Sending events
Send an event to POST /v1/webhooks when something changes in your system: a booking made, moved or cancelled. Send
one event per call. Your key needs the webhooks scope.
The body
Section titled “The body”A JSON object:
{ "id": "evt_8f2c1a", "type": "booking.created", "created_at": "2026-11-11T12:00:00Z", "data": { "...": "agreed with Mayotte Hub for each type" }}| Field | Required | What it is |
|---|---|---|
id |
yes | Your event’s own id: up to 200 letters, digits and . _ : -. Use the same id when you send the event again. If your events have no id, send an Idempotency-Key header instead. |
type |
yes | What happened, for example booking.created, booking.updated or booking.cancelled. |
created_at |
no | When it happened in your system. |
data |
no | Depends on the type. We agree its fields with you. |
The headers
Section titled “The headers”Authorization: Bearer mh_live_…Content-Type: application/jsonX-Mayotte-Timestamp: 1762862400X-Mayotte-Signature: v1=<64 lowercase hex characters>X-Mayotte-Timestampis the time you sign, in Unix seconds. It must be within 5 minutes of our clock, so sign each event when you send it, and sign it again when you resend it.X-Mayotte-Signatureisv1=and the HMAC-SHA256 of<timestamp>.<body>, written in lowercase hex. The key of the HMAC is your whole signing secret,mh_whsec_….
Examples
Section titled “Examples”Node.js
Section titled “Node.js”import crypto from 'node:crypto'
const body = JSON.stringify({ id: 'evt_8f2c1a', type: 'booking.created', created_at: new Date().toISOString(), data: {} })const timestamp = String(Math.floor(Date.now() / 1000))const signature = crypto.createHmac('sha256', process.env.MAYOTTE_SIGNING_SECRET).update(`${timestamp}.${body}`).digest('hex')
const res = await fetch('https://api.mayottehub.com/v1/webhooks', { method: 'POST', headers: { Authorization: `Bearer ${process.env.MAYOTTE_KEY}`, 'Content-Type': 'application/json', 'X-Mayotte-Timestamp': timestamp, 'X-Mayotte-Signature': `v1=${signature}`, }, body,})Python
Section titled “Python”import hashlib, hmac, json, os, time, urllib.request
body = json.dumps({"id": "evt_8f2c1a", "type": "booking.created", "data": {}}, separators=(",", ":")).encode()timestamp = str(int(time.time()))signature = hmac.new(os.environ["MAYOTTE_SIGNING_SECRET"].encode(), timestamp.encode() + b"." + body, hashlib.sha256).hexdigest()
req = urllib.request.Request("https://api.mayottehub.com/v1/webhooks", data=body, method="POST", headers={ "Authorization": "Bearer " + os.environ["MAYOTTE_KEY"], "Content-Type": "application/json", "X-Mayotte-Timestamp": timestamp, "X-Mayotte-Signature": "v1=" + signature,})print(urllib.request.urlopen(req).read().decode())BODY='{"id":"evt_8f2c1a","type":"booking.created","data":{}}'TS=$(date +%s)SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$MAYOTTE_SIGNING_SECRET" -hex | sed 's/^.* //')curl https://api.mayottehub.com/v1/webhooks \ -H "Authorization: Bearer $MAYOTTE_KEY" -H "Content-Type: application/json" \ -H "X-Mayotte-Timestamp: $TS" -H "X-Mayotte-Signature: v1=$SIG" \ --data "$BODY"The answer
Section titled “The answer”{ "ok": true, "id": "evt_8f2c1a", "duplicate": false }duplicate is true when this key already sent an event with this id. The event is not kept twice.
Retries
Section titled “Retries”200means the event is kept, even whenduplicateistrue. Do not send it again.429,5xxor no answer: send it again later with the sameid. Wait longer each time: 1, 5, then 15 minutes.- Another
4xx: fix the call first. Sending the same call again gets the same answer.
Changing your signing secret
Section titled “Changing your signing secret”While you move to a new signing secret, you may send two signatures in one header, v1=<old>,v1=<new>. The event is
accepted when one of them matches.