Skip to content

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.

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.
Authorization: Bearer mh_live_…
Content-Type: application/json
X-Mayotte-Timestamp: 1762862400
X-Mayotte-Signature: v1=<64 lowercase hex characters>
  • X-Mayotte-Timestamp is 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-Signature is v1= and the HMAC-SHA256 of <timestamp>.<body>, written in lowercase hex. The key of the HMAC is your whole signing secret, mh_whsec_….
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,
})
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())
Terminal window
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"
{ "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.

  • 200 means the event is kept, even when duplicate is true. Do not send it again.
  • 429, 5xx or no answer: send it again later with the same id. Wait longer each time: 1, 5, then 15 minutes.
  • Another 4xx: fix the call first. Sending the same call again gets the same answer.

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.