Help API

    /v1/webhooks

    Register and manage webhook subscriptions programmatically — the same real-time callbacks you can set up by hand on the Webhooks tab of Admin Center → API, but driven from your own code so an installed integration can wire up its own delivery endpoint without an admin clicking through the dashboard.

    For the delivery contract itself — the request envelope, the X-42min-Webhook-Signature header, how to verify it, the retry schedule, and auto-pause behavior — see API & webhooks. This page is the management surface; that page is the receiving side.


    The per-credential sandbox

    This is the one rule that makes /v1/webhooks behave differently from the rest of the API:

    A credential only ever sees and mutates the webhooks it created.

    Concretely:

    • Webhooks created through this API by a PAT are owned by that token. Webhooks created by an OAuth client are owned by that client (all of the client's access tokens share one sandbox — the binding is the stable client_id, so a token refresh doesn't orphan anything; a PAT, by contrast, is bound to that specific token, so rotating the PAT leaves its webhooks unreachable from the new token).
    • Webhooks created in the admin UI are invisible to /v1/webhooks, and webhooks created over the API don't appear in another credential's listing. The admin UI still sees every webhook in the account — the sandbox is one-directional.
    • Two different integrations that legitimately point at the same ingestion URL are two real subscribers and both receive every matching event — the duplicate-URL check is scoped to your own sandbox, not the whole account.

    Because of the sandbox, GET /v1/webhooks returning [] does not mean the account has no webhooks — only that this credential created none.

    Conventions specific to this resource

    • Scopeswebhooks:read for the GETs, webhooks:write for every mutation (create, update, delete, rotate-secret, test). See Scopes.
    • No Idempotency-Key, no If-Match. Unlike /v1/bookings, the webhook endpoints don't take an idempotency key or an ETag — they aren't optimistically locked. Last write wins.
    • The signing secret is shown exactly once. It is server-generated (you can't supply your own) and appears as signing_secret only in the POST (create) and rotate-secret responses. It is never returned by any GET. Lost it? Rotate.
    • Account ceiling: 42 webhooks. The cap is account-wide across every credential and the admin UI combined — not 42 per credential.

    The webhook object

    {
      "id": "01H…",
      "url": "https://hooks.example.com/42min",
      "events": ["booking_created", "booking_updated"],
      "status": "active",
      "description": "Prod CRM sync",
      "paused_reason": null,
      "last_delivery_at": "2026-05-18T14:03:00.000Z",
      "last_delivery_ok": true,
      "created_at": "2026-05-10T09:00:00.000Z",
      "updated_at": "2026-05-18T14:03:00.000Z",
      "signing_secret": "42min_wh_…"
    }
    
    • status is active or paused. paused_reason is set when 42min auto-paused the hook (after 5 consecutive failed deliveries) and is null otherwise.
    • last_delivery_ok is true / false / null (never delivered yet).
    • signing_secret is present only on create and rotate-secret responses.

    GET /v1/webhooks

    List the webhooks this credential created.

    Method GET
    URL https://api.42min.us/v1/webhooks
    Scope webhooks:read
    Auth Required

    Returns a plain array (newest first) — this endpoint is not paginated.

    {
      "data": [ { "id": "01H…", "url": "https://…", "events": ["booking_created"], "status": "active", "…": "…" } ],
      "meta": { "request_id": "req_…" }
    }
    

    curl

    curl -H "Authorization: Bearer $TOKEN" \
      https://api.42min.us/v1/webhooks
    

    GET /v1/webhooks/:id

    Return one webhook this credential created.

    Method GET
    URL https://api.42min.us/v1/webhooks/{id}
    Scope webhooks:read
    Auth Required

    Common errors

    • 404 webhook.notFound — no such webhook in this credential's sandbox (it may exist for another credential or in the admin UI — you just can't see it).

    POST /v1/webhooks

    Create a webhook subscription.

    Method POST
    URL https://api.42min.us/v1/webhooks
    Scope webhooks:write
    Auth Required

    Body

    {
      "url": "https://hooks.example.com/42min",
      "events": ["booking_created", "booking_updated", "event_type_updated"],
      "description": "Prod CRM sync"
    }
    
    Field Type Notes
    url string Required. Must be https:// and resolve to a public address — http://, localhost, and private/loopback IP ranges are rejected. Max 2000 chars. Normalized (fragment and trailing slash stripped) before storage.
    events string[] Required, at least one. See Event names — use the underscore form here.
    description string Optional. Max 255 chars.

    Response

    201 Created with the webhook object including signing_secret — this is the only GET-style payload that ever carries the secret on create. Store it now; you'll need it to verify deliveries and it is never shown again.

    curl

    curl -X POST https://api.42min.us/v1/webhooks \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "url": "https://hooks.example.com/42min",
        "events": ["booking_created", "booking_updated"],
        "description": "Prod CRM sync"
      }'
    

    Common errors

    • 400url isn't a public HTTPS endpoint, or events is empty / contains an unknown event name.
    • 409 webhook.duplicateUrlthis credential already has a webhook on that URL.
    • 409 — the account is at the 42-webhook ceiling.

    PATCH /v1/webhooks/:id

    Update a webhook. All fields optional; only those present are changed.

    Method PATCH
    URL https://api.42min.us/v1/webhooks/{id}
    Scope webhooks:write
    Auth Required

    Body

    {
      "url": "https://hooks.example.com/42min/v2",
      "events": ["booking_created", "booking_canceled"],
      "description": "Prod CRM sync (v2)",
      "status": "active"
    }
    
    Field Type Notes
    url string Same rules as create. Changing it re-runs the HTTPS/public check and the per-sandbox duplicate check.
    events string[] Replaces the event list wholesale (at least one).
    description string Max 255.
    status string active or paused. Setting it to active resets the consecutive-failure counter and clears paused_reason — this is how you re-enable an auto-paused hook.

    Response

    200 OK with the updated webhook object (no signing_secret).

    curl

    # Resume an auto-paused webhook
    curl -X PATCH https://api.42min.us/v1/webhooks/01H… \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -d '{"status":"active"}'
    

    Common errors

    • 404 webhook.notFound — not in this credential's sandbox.
    • 400 / 409 — same URL validation and duplicate-URL rules as create.

    DELETE /v1/webhooks/:id

    Permanently delete a webhook and its delivery history.

    Method DELETE
    URL https://api.42min.us/v1/webhooks/{id}
    Scope webhooks:write
    Auth Required

    Response

    204 No Content — empty body.

    Common errors

    • 404 webhook.notFound — not in this credential's sandbox.

    POST /v1/webhooks/:id/rotate-secret

    Issue a new signing secret. The old secret stops being valid immediately — update your verifier before you call this, or accept a brief window of signature failures.

    Method POST
    URL https://api.42min.us/v1/webhooks/{id}/rotate-secret
    Scope webhooks:write
    Auth Required

    Response

    200 OK with the webhook object including the new signing_secret — the second and last place the secret is ever returned.

    curl

    curl -X POST https://api.42min.us/v1/webhooks/01H…/rotate-secret \
      -H "Authorization: Bearer $TOKEN"
    

    POST /v1/webhooks/:id/test

    Enqueue a synthetic booking.created delivery so you can confirm your endpoint is reachable and your signature check works. The payload's data.booking is obviously fake ("test": true, id: "test-booking-id").

    Method POST
    URL https://api.42min.us/v1/webhooks/{id}/test
    Scope webhooks:write
    Auth Required

    Response

    200 OK:

    { "data": { "ok": true, "delivery_id": "01H…" }, "meta": { "request_id": "req_…" } }
    

    The delivery is queued, not synchronous — poll /deliveries with the returned delivery_id to see the outcome.


    GET /v1/webhooks/:id/deliveries

    The 50 most recent delivery attempts for this webhook, newest first. Older attempts are pruned after 30 days.

    Method GET
    URL https://api.42min.us/v1/webhooks/{id}/deliveries
    Scope webhooks:read
    Auth Required

    Response

    {
      "data": [
        {
          "id": "01H…",
          "event": "booking_created",
          "attempt": 1,
          "status_code": 200,
          "error": null,
          "delivered_at": "2026-05-18T14:03:01.000Z",
          "created_at": "2026-05-18T14:03:00.000Z"
        }
      ],
      "meta": { "request_id": "req_…" }
    }
    
    • status_code is the HTTP status your endpoint returned (null if the request never completed — DNS failure, timeout, connection refused).
    • error carries the failure reason when there is one; null on success.
    • delivered_at is set only once a 2xx is received; it stays null for attempts that failed or are still pending.

    Event names

    The events array you send to create/update uses the enum (underscore) form. The event field in the delivered envelope and the X-42min-Webhook-Event header use the dot form. They map one-to-one:

    Send in events Delivered as Fires when
    booking_created booking.created A meeting is booked.
    booking_rescheduled booking.rescheduled A booking moves to a new time.
    booking_canceled booking.canceled A booking is canceled.
    booking_no_show booking.no_show A booking is marked no-show.
    booking_updated booking.updated PATCH /v1/bookings/:uid changed metadata, responses, or attendee_name. The payload adds changed_fields[] naming exactly which of those changed. This is the only path that mutates those fields silently — no booking.rescheduled/booking.canceled covers it. See /v1/bookings.
    event_type_updated event_type.updated An event type is created-via-edit, edited, or toggled on/off from the Event Types dashboard. Payload data.event_type is the same shape as GET /v1/event-types/:idOrSlug (locations, questions[], limits — all of it).
    event_type_deleted event_type.deleted An event type is deleted. Payload carries only { id, slug, deleted_at } — the row is gone.

    event_type.updated / event_type.deleted fire from dashboard changes — there are no public write endpoints for event types yet, but the webhooks let an integration keep its mirror of your event-type catalog in sync.

    Catch-up after a pause. If a webhook was auto-paused and you missed booking.* events, reconcile with GET /v1/bookings?updated_since=…&sort=updated_at_asc rather than guessing — it returns every booking touched since a timestamp, in stable ascending order, ready to page through.


    Where this sits

    • Receiving, verifying, retries, auto-pause, the activity log → API & webhooks.
    • Minting the PAT or OAuth client that calls this API → Authentication.
    • The exact scope each endpoint needs → Scopes.

    Last updated May 19, 2026.