Skip to content

Webhooks

When something happens in a club that has granted your application the events.read scope, Lobo One can tell you — by POSTing to an address you register, signed, retried, and kept in a log you can read.

Subscribing

A subscription belongs to one application and names one club. The club must already have granted the application events.read; a subscription cannot reach a club that has not consented, and it stops delivering the moment the grant is withdrawn — checked when each event is fanned out, not when the subscription was written.

httpcreating a subscription
POST /v1/developer-organizations/{developerOrganizationId}/applications/{applicationId}/webhook-subscriptions
{
  "tenantId": "…",
  "url": "https://hooks.example.com/lobo-one",
  "eventNames": ["members.member_created", "bookings.booking_confirmed"],
  "description": "CRM sync"
}

The response carries the signing secret once. It is stored nowhere you can read it again; rotate it if it is lost. An address must be https, must name a host on the public internet, and must not be an IP address — and at delivery time the name is resolved and refused if it points anywhere private.

What you receive

httpa delivery
POST https://hooks.example.com/lobo-one
Content-Type: application/json
lobo-one-event: members.member_created
lobo-one-event-id: 01a0…
lobo-one-delivery-id: 01a0…
lobo-one-timestamp: 1757851200
lobo-one-signature: t=1757851200,v1=5f2c…

{
  "id": "01a0…",
  "type": "members.member_created",
  "version": 1,
  "occurredAt": "2026-09-14T12:00:00.000Z",
  "tenantId": "…",
  "data": { "memberId": "…", "tenantId": "…" }
}

data is the payload the owning context published: identifiers and the few facts the event is about, versioned by version. Fetch what you need through the API with your grant; a webhook is a notice that something changed, not a copy of it.

Verifying

The signature is HMAC-SHA256(secret, `${t}.${rawBody}`), hex. Compute it over the raw body — a body re-serialised by your framework is different bytes — compare in constant time, and refuse a timestamp older than a few minutes. For a day after you rotate the secret, deliveries carry a v1= under each secret, so a fleet can switch over without missing anything. @lobo-one/sdk/webhooks does all of this in one call.

Delivery, retries and replay

  • A 2xx is delivered. A 5xx, a 408, a 429, a timeout or a connection failure is retried with growing backoff — 30 seconds, a minute, two, four — up to eight times.
  • Any other 4xx is not retried: a 404 will be a 404 in ten minutes. The delivery is set aside as dead.
  • Redirects are not followed. A redirect is a new address nobody checked.
  • A dead delivery can be replayed: the same bytes, to the current address, signed with the current secret. Every attempt — status, timing, reason — is in the log.
  • A subscription whose deliveries keep dying is disabled automatically, with the reason kept. Pausing is different: paused deliveries queue and drain when you resume.
  • The same event is delivered at most once per subscription. If you see the same lobo-one-event-id twice, it was a replay somebody asked for.

Every event you can subscribe to

The public event catalogue
EventWhat happenedAlways in the payload
members.member_createdSomebody was registered as a member of the club.
  • memberId
  • tenantId
members.member_status_changedA member became active, suspended or left.
  • memberId
  • tenantId
  • status
membership.membership_activatedA membership started.
  • membershipId
  • memberId
  • tenantId
membership.membership_suspendedA membership was suspended.
  • membershipId
  • memberId
  • tenantId
membership.membership_reinstatedA suspended membership was reinstated.
  • membershipId
  • memberId
  • tenantId
membership.membership_terminatedA membership ended.
  • membershipId
  • memberId
  • tenantId
subscriptions.subscription_activatedA subscription started.
  • subscriptionId
  • memberId
  • tenantId
subscriptions.subscription_renewedA subscription renewed for another period.
  • subscriptionId
  • memberId
  • tenantId
subscriptions.subscription_cancelledA subscription was cancelled.
  • subscriptionId
  • memberId
  • tenantId
subscriptions.subscription_expiredA subscription reached its end without renewing.
  • subscriptionId
  • memberId
  • tenantId
bookings.booking_confirmedA booking was confirmed.
  • bookingId
  • memberId
  • tenantId
bookings.booking_cancelledA booking was cancelled.
  • bookingId
  • memberId
  • tenantId
bookings.booking_checked_inThe member arrived for a booking.
  • bookingId
  • memberId
  • tenantId
bookings.booking_no_showA booking passed without the member arriving.
  • bookingId
  • memberId
  • tenantId
services.service_bookedA bookable service was booked.
  • serviceBookingId
  • memberId
  • tenantId
services.service_cancelledA booked service was cancelled.
  • serviceBookingId
  • memberId
  • tenantId
commerce.order_confirmedAn order was confirmed.
  • orderId
  • memberId
  • tenantId
commerce.order_cancelledAn order was cancelled.
  • orderId
  • memberId
  • tenantId
billing.receivable_issuedSomething became owed.
  • receivableId
  • memberId
  • tenantId
billing.receivable_settledSomething owed was paid.
  • receivableId
  • memberId
  • tenantId
payments.payment_failedA payment attempt failed.
  • paymentId
  • memberId
  • tenantId
wallet.wallet_creditedA member’s wallet was credited.
  • walletId
  • memberId
  • tenantId
Note

Nothing outside this catalogue is delivered to anybody. The platform’s own administrative events — a credential issued, a support session opened — are not on it and cannot be subscribed to.