エンドポイント
予約の項目のうち、status は pending・confirmed・cancelled・rescheduled・failed・no_show、
source(予約の経路)は public(予約ページ)・internal(社内)・api・poll(日程投票)のいずれかです。
GET /api/v1/event-types
調整ページの一覧 — 公開中(status が active)の調整ページを最大 100 件返します。社内メモは含みません。
パラメータはありません。
リクエストの例(curl) コピー
curl https://fixday.app/api/v1/event-types \
-H "Authorization: Bearer fdk_live_xxxxxxxxxxxxxxxxxxxx"
レスポンスの例(200) コピー
{
"eventTypes": [
{
"id": "01JAXQ3N4P5R6S7T8V9W0X1Y2Z",
"slug": "30min",
"title": "30分のオンライン相談",
"description": "サービスの導入についてのご相談を承ります。",
"durationMinutes": 30,
"status": "active",
"folderId": null,
"createdAt": 1790000000000,
"updatedAt": 1790500000000
}
]
}
エラー: 401 ・ 429
GET /api/v1/event-types/{id}/slots
空き枠の取得 — 受付時間・カレンダーの予定・上限などを反映した、いま予約できる枠を返します。対象期間は調整ページの「予約を受け付ける期間(今日から何日先まで)」の範囲で、from / to はその中をさらに絞り込みます。
名前 場所 型 説明 id必須 path string 調整ページの ID(一覧の id) fromquery integer この時刻以降に始まる枠だけ(epoch ミリ秒) toquery integer この時刻より前に始まる枠だけ(epoch ミリ秒)
リクエストの例(curl) コピー
curl "https://fixday.app/api/v1/event-types/01JAXQ3N4P5R6S7T8V9W0X1Y2Z/slots?from=1791442800000" \
-H "Authorization: Bearer fdk_live_xxxxxxxxxxxxxxxxxxxx"
レスポンスの例(200) コピー
{
"eventTypeId": "01JAXQ3N4P5R6S7T8V9W0X1Y2Z",
"timezone": "Asia/Tokyo",
"durationMinutes": 30,
"slots": [
{
"start": 1791442800000,
"end": 1791444600000
},
{
"start": 1791444600000,
"end": 1791446400000
}
],
"unavailableHostCount": 0
}
unavailableHostCount が 1 以上のときは、カレンダーを読めなかった・予定を登録できない担当者(連携が切れている・カレンダーを接続していない)がいて、候補が本来より少なく出ています。
エラー: 400 ・ 401 ・ 404 ・ 429
GET /api/v1/bookings
予約の一覧 — 開始日時の新しい順に返します。limit(既定 50・最大 100)と offset で分けて取得します。
名前 場所 型 説明 fromquery integer 開始日時がこの時刻以降(epoch ミリ秒) toquery integer 開始日時がこの時刻以前(epoch ミリ秒) statusquery string 状態で絞り込む(pending / confirmed / cancelled / rescheduled / failed / no_show) eventTypeIdquery string 調整ページで絞り込む limitquery integer 件数(1〜100。範囲外は丸めます) offsetquery integer 読み飛ばす件数(既定 0)
リクエストの例(curl) コピー
curl "https://fixday.app/api/v1/bookings?status=confirmed&limit=50&offset=0" \
-H "Authorization: Bearer fdk_live_xxxxxxxxxxxxxxxxxxxx"
レスポンスの例(200) コピー
{
"bookings": [
{
"id": "01JB2Y7Q8M3K4N5P6R7S8T9V0W",
"eventTypeId": "01JAXQ3N4P5R6S7T8V9W0X1Y2Z",
"status": "confirmed",
"startAt": 1791442800000,
"endAt": 1791444600000,
"guestTimezone": "Asia/Tokyo",
"guestName": "予約 一郎",
"guestEmail": "ichiro@example.com",
"guestPhone": null,
"meetingUrl": "https://meet.google.com/abc-defg-hij",
"source": "public",
"cancelReason": null,
"cancelledAt": null,
"createdAt": 1791100000000,
"updatedAt": 1791100000000
}
],
"hasMore": false,
"limit": 50,
"offset": 0
}
エラー: 401 ・ 429
GET /api/v1/bookings/{id}
予約の明細 — 予約 1 件の内容と、予約時の質問への回答(answers)を返します。
名前 場所 型 説明 id必須 path string 予約の ID
リクエストの例(curl) コピー
curl https://fixday.app/api/v1/bookings/01JB2Y7Q8M3K4N5P6R7S8T9V0W \
-H "Authorization: Bearer fdk_live_xxxxxxxxxxxxxxxxxxxx"
レスポンスの例(200) コピー
{
"booking": {
"id": "01JB2Y7Q8M3K4N5P6R7S8T9V0W",
"eventTypeId": "01JAXQ3N4P5R6S7T8V9W0X1Y2Z",
"status": "confirmed",
"startAt": 1791442800000,
"endAt": 1791444600000,
"guestTimezone": "Asia/Tokyo",
"guestName": "予約 一郎",
"guestEmail": "ichiro@example.com",
"guestPhone": null,
"meetingUrl": "https://meet.google.com/abc-defg-hij",
"source": "public",
"cancelReason": null,
"cancelledAt": null,
"createdAt": 1791100000000,
"updatedAt": 1791100000000,
"answers": [
{
"label": "会社名",
"value": "株式会社サンプル"
}
]
}
}
エラー: 401 ・ 404 ・ 429
POST /api/v1/bookings/{id}/cancel
予約のキャンセル — ゲストにキャンセルのメールが届き、カレンダーの予定も消えます。事前決済のある予約は、返金の決まりに沿って返金されます。すでにキャンセル済みの予約に対しても ok: true を返すので、何度呼んでもかまいません。
名前 場所 型 説明 id必須 path string 予約の ID reasonbody (JSON) string キャンセルの理由(500 文字まで。超えた分は切り捨て)
リクエストの例(curl) コピー
curl -X POST https://fixday.app/api/v1/bookings/01JB2Y7Q8M3K4N5P6R7S8T9V0W/cancel \
-H "Authorization: Bearer fdk_live_xxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"reason":"先方のご都合によりキャンセル"}'
レスポンスの例(200) コピー
{
"ok": true,
"orphanedEventCount": 0
}
orphanedEventCount が 1 以上のときは、カレンダーから消せなかった予定があります。お手数ですがカレンダー側で消してください。
エラー: 401 ・ 404 ・ 429 ・ 500
Webhook
予約が確定・変更・キャンセルされたときに、指定した URL へ JSON を POST します。
管理画面の「外部への通知」で[連携を追加]を押し、種類に webhook を選んで URL を登録してください(すべてのプランで使えます。登録できる数は無料プランで 2 件からプランに応じて増えます)。
URL は https のみ です。社内ネットワークや IP アドレスを指す URL は登録できません。
リダイレクトは追いません。 3xx を返すと届かなかった扱いになります。最終的な URL を登録してください。
応答は 10 秒以内に返してください。重い処理は受け取ったあとで行ってください。
登録すると署名鍵(whsec_ で始まる文字列)が一度だけ表示されます 。控えておいてください。
「テスト送信」で、架空の予約(booking.id が test)を 1 通送れます。
イベントと本文
event 送られるとき booking.confirmed予約が確定した(承認フローのある予約は承認されたとき) booking.rescheduled予約の日程が変更された booking.cancelled予約がキャンセルされた(承認の却下・期限切れを含む) booking.reminder予約者へリマインドのメールを送った(調整ページで設定したリマインドの時刻ごとに 1 回)
管理画面から登録した Webhook は、booking.confirmed・booking.rescheduled・booking.cancelled の 3 つを受け取ります。
登録の途中で「リマインドのメールを送ったときも知らせますか?」に OK を押すと、booking.reminder も受け取ります(予約者へリマインドのメールを送ったあとに、設定したリマインドの時刻ごとに 1 回)。
booking.reminder の本文には、何分前のリマインドかを表す reminderMinutes(1440 なら 24 時間前。分からない古いリマインドでは null)が付きます。配信 ID(id)はリマインド 1 件ごとに決まった値なので、同じリマインドが 2 回届いても id で重複を除けます。
承認の却下や期限切れによるキャンセルも booking.cancelled で届き、booking.approvalState(rejected / expired)で区別できます。
日程変更では、新しい予約として作り直すため、元の予約の booking.cancelled(cancelReason に日程変更である旨が入ります)と、新しい予約の booking.rescheduled の 2 つが届きます(届く順番は前後することがあります)。
ヘッダー:
X-FixDay-Signature — 署名(下記)
X-FixDay-Event — イベント名(本文を読む前に振り分けられます)
X-FixDay-Delivery — 配信 ID。再送しても変わりません(本文の id と同じ)
User-Agent — FixDay-Webhook/1.0
本文の例(booking.confirmed) コピー
{
"id": "01JB2Y9ZQ0DELIVERY00000000",
"event": "booking.confirmed",
"occurredAt": 1791100000000,
"teamId": "01JAT0TEAM000000000000000",
"teamName": "株式会社サンプル",
"booking": {
"id": "01JB2Y7Q8M3K4N5P6R7S8T9V0W",
"eventTypeId": "01JAXQ3N4P5R6S7T8V9W0X1Y2Z",
"eventTitle": "30分のオンライン相談",
"status": "confirmed",
"approvalState": null,
"startAt": 1791442800000,
"endAt": 1791444600000,
"timezone": "Asia/Tokyo",
"guestName": "予約 一郎",
"guestEmail": "ichiro@example.com",
"hostNames": [
"見本 太郎"
],
"meetingUrl": "https://meet.google.com/abc-defg-hij",
"manageUrl": "https://fixday.app/b/abcdefghij",
"cancelReason": null
}
}
本文の例(booking.reminder) コピー
{
"id": "reminder_01JB2YJOB000000000000000.01JAT0INTEGRATION0000000",
"event": "booking.reminder",
"occurredAt": 1791100000000,
"teamId": "01JAT0TEAM000000000000000",
"teamName": "株式会社サンプル",
"booking": {
"id": "01JB2Y7Q8M3K4N5P6R7S8T9V0W",
"eventTypeId": "01JAXQ3N4P5R6S7T8V9W0X1Y2Z",
"eventTitle": "30分のオンライン相談",
"status": "confirmed",
"approvalState": null,
"startAt": 1791442800000,
"endAt": 1791444600000,
"timezone": "Asia/Tokyo",
"guestName": "予約 一郎",
"guestEmail": "ichiro@example.com",
"hostNames": [
"見本 太郎"
],
"meetingUrl": "https://meet.google.com/abc-defg-hij",
"manageUrl": "https://fixday.app/b/abcdefghij",
"cancelReason": null
},
"reminderMinutes": 1440
}
日時の表示用のタイムゾーン(booking.timezone)はチームのものです。manageUrl はゲストが予約を変更・キャンセルするための URL で、ゲスト本人以外に渡さないでください。
署名の検証
X-FixDay-Signature: t=<UNIX 秒>,v1=<16 進の HMAC-SHA256> の形です。
「t の値」「.」「本文そのもの」をつないだ文字列を、署名鍵(whsec_ を含めた全体)で HMAC-SHA256 した値が v1 です(Stripe と同じ方式)。
本文は受け取ったままの文字列で 検証してください。JSON を解釈してから文字列に戻すと一致しません。
t が 300 秒より古い(または未来の)ものは捨ててください。盗まれた本文の再送を防げます。
比較は定数時間で行ってください(下の例の timingSafeEqual / compare_digest)。
Node.js コピー
import crypto from 'node:crypto'
// rawBody は JSON.parse する前の本文(文字列)。parse してから文字列に戻すと一致しません
export function verifyFixDaySignature(rawBody, header, secret, toleranceSec = 300) {
let t = null
const sigs = []
for (const part of (header || '').split(',')) {
const [k, v] = part.split('=')
if (k?.trim() === 't') t = v
if (k?.trim() === 'v1' && v) sigs.push(v.trim())
}
if (!t || sigs.length === 0) return false
// 古い署名を弾く(盗んだ本文の再送を防ぐ)
if (Math.abs(Date.now() / 1000 - Number(t)) > toleranceSec) return false
const expected = crypto.createHmac('sha256', secret).update(t + '.' + rawBody).digest('hex')
return sigs.some((s) => s.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(s), Buffer.from(expected)))
}
// Express の例: 本文を文字列のまま受け取る
app.post('/fixday-webhook', express.text({ type: 'application/json' }), (req, res) => {
if (!verifyFixDaySignature(req.body, req.get('X-FixDay-Signature'), process.env.FIXDAY_WEBHOOK_SECRET)) {
return res.status(400).end()
}
const event = JSON.parse(req.body)
// event.id(= X-FixDay-Delivery)で重複を除いてから処理する
res.status(200).end()
})
Python コピー
import hashlib, hmac, time
def verify_fixday_signature(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
t, sigs = None, []
for part in (header or '').split(','):
k, _, v = part.partition('=')
if k.strip() == 't':
t = v.strip()
elif k.strip() == 'v1' and v:
sigs.append(v.strip())
if not t or not sigs:
return False
if 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(s, expected) for s in sigs)
再送と自動停止
2xx 以外の応答・タイムアウト・接続失敗は失敗として、間隔を空けて送り直します(30 秒 → 1 分 → 2 分 → 4 分。最初の送信を含めて最大 5 回)。
同じイベントが 2 回以上届くことがあります。 X-FixDay-Delivery で重複を除いてください。
届く順番は発生順と前後することがあります。occurredAt で並べ直してください。
送り直すのは、送信を試みたあとの失敗だけです。 予約の確定・変更・キャンセル・リマインドのどれでも、FixDay 側で送信待ちに積むこと自体に失敗したイベント(まれです)は送り直しません。booking.reminder の場合、リマインドのメールは届いていても Webhook は届かないことになります。取りこぼしが困る用途では、公開 API の予約一覧と定期的に突き合わせてください。
5 回とも失敗したイベントが 3 件続くと、その連携は自動で停止 し、管理画面に理由が表示されます。直したら画面から再開してください。
Slack・Chatwork・Microsoft Teams への通知は、同じ画面から種類を選ぶだけで使えます(署名の検証は不要です)。