Skip to content

Webhooks & Integration Events

Memvera pushes signed JSON events to your systems (ERP, communication platforms, BI) as they happen — no polling. A gym admin configures endpoints under Settings → Webhooks in the staff portal: the destination URL, which events to receive, optional auth headers (e.g. your API key), and a signing secret.

Event catalog

DomainEvents
Membershipsmembership.contract.activated (sold) · membership.contract.renewed · membership.contract.cancelled · .frozen / .unfrozen · .expired · .rewritten · .blocked / .unblocked · .plan_changed · .drafted · membership.reactivated · membership.renewal.failed · membership.grace.expired · membership.agreement.generated · cancellation requested/withdrawn
Schedulingscheduling.booking.created · scheduling.booking.cancelled · scheduling.booking.attended · scheduling.booking.no_show · scheduling.session.conducted
Financefinance.payment.settled · finance.refund.issued · finance.retail.sale.completed · finance.cash.variance_flagged
People & accessidentity.member.created (members and leads — see profile_type) · facility.member.checked_in
Fitnessworkouts.activity.recorded

Subscribe to specific events or * for all.

Example payloads

scheduling.booking.created:

json
{
  "booking_id": "01j8...", "brand_id": "01hz...", "member_id": "01j2...",
  "session_id": "01j8...", "session_type": "gx", "class_name": "HIIT 45",
  "starts_at": "2026-08-05T18:00:00+03:00", "club_id": "01hz...", "status": "booked"
}

scheduling.booking.cancelledreason is one of member, staff, freeze, session_cancelled:

json
{
  "booking_id": "01j8...", "brand_id": "01hz...", "member_id": "01j2...",
  "session_id": "01j8...", "starts_at": "2026-08-05T18:00:00+03:00",
  "reason": "freeze", "previous_status": "booked"
}

membership.contract.renewed:

json
{
  "contract_id": "01j9...", "brand_id": "01hz...", "member_id": "01j2...",
  "plan_id": "01hz...", "amount_minor": 150000, "currency": "SAR",
  "activation_date": "2026-08-04T10:30:00+03:00", "duration_months": 12
}

Delivery contract

Every delivery is an HTTP POST with these headers:

HeaderMeaning
X-Memvera-EventThe event name (scheduling.booking.created, …)
X-Memvera-DeliveryUnique delivery ID — dedupe on this; retries reuse it
X-Memvera-SignatureHMAC-SHA256(body, your signing secret), hex
(your custom headers)Whatever the admin configured (API keys etc.)

Respond with any 2xx within 15 seconds. Non-2xx or timeouts are retried up to 5 times with linear backoff (1, 2, 3, 4 minutes), then dead-lettered — visible (and re-deliverable) in Settings → Webhooks → Deliveries.

Verifying the signature

Node.js:

js
import crypto from "node:crypto";

function verify(rawBody, signatureHeader, secret) {
  const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
}

PHP:

php
$expected = hash_hmac('sha256', $rawBody, $secret);
$valid = hash_equals($expected, $request->header('X-Memvera-Signature'));

Compute the HMAC over the raw request body

Parse the JSON only after verifying — re-serialising can reorder keys and break the signature.

Testing

Use the Send test button next to your subscription — it fires a test.ping event and shows your endpoint's response code. webhook.site is handy while developing.