Webhooks
Send club events such as new members, bookings and payments to Zapier, Make, Power Automate or your own code the moment they happen in ClubPal.
A webhook tells another service the moment something happens in ClubPal. You give ClubPal a web address (an endpoint), choose which events to send, and every time one of them happens ClubPal sends the details to that address. The service at the other end, whether that is Zapier, Make, Power Automate or code your club runs itself, can then act on it.
Clubs use webhooks to build the automation that suits them rather than waiting for ClubPal to build it:
- Onboarding. When someone joins, add them to your mailing list, send a welcome pack, or create a task for the membership secretary.
- Leaver journeys. When a membership ends or an automatic renewal fails, send a "we miss you" email a week later, or ask why they left.
- Waiting lists. When someone joins a waiting list, or moves from it into a place, post a message to the coaches' group chat.
- Cross-posting. When a blog post goes live, share it on Facebook or in your newsletter tool.
- Reporting. Copy payments, orders or attendance into a spreadsheet or your accounts package.
Webhooks are available to every club, on every plan.
Before you start
- Only a System Administrator can manage webhooks. The Webhooks page needs the System Administrator permission, because an endpoint sends club data outside ClubPal. See Permissions.
- Each endpoint sends members' personal data to the service at that URL. Events include the details of the people they are about, such as names, email addresses, phone numbers and dates of birth, and they go to whoever runs the address you enter. Your club is responsible for that service and for how it uses the details, so check it fits your club's privacy notice before adding it. Card details, medical information and form answers are never included.
- The address must start with
https://and be reachable on the public internet. Addresses on a private or internal network are refused. - A club can have up to 10 endpoints.
ClubPal keeps a record in Admin > Audit Log of who added, changed or deleted each endpoint, so you can always see who pointed your club's data at which address.
Set up an endpoint
- Get a web address from the service that will receive the events. The recipes below show where Zapier, Make and Power Automate give you one.
- In ClubPal, go to Admin > Webhooks and click Add endpoint.
- Paste the address into Endpoint URL.
- Optionally add a Description, a note for other administrators such as "Welcome emails in Zapier".
- Under Events to send, tick the events this endpoint should receive. The box on each group's heading ticks every event in that group, and Select all ticks everything; each heading shows how many of its events are ticked. Type in Search events to find an event by its name or what it does (the group boxes and Select all then tick only the events the search shows), and turn on Selected only to check what the endpoint will receive. Choose only what the automation needs: fewer events means less personal data leaves ClubPal.
- Click Save endpoint.
- ClubPal shows the endpoint's Signing secret, starting
whsec_. Click Copy and keep it somewhere safe, or paste it straight into your code. It is not shown again. If you lose it, use Roll secret on the endpoint's page to get a new one.
The API version shown on the form is the shape of the events this endpoint receives. It stays fixed for the life of the endpoint, so when ClubPal adds a new version your automations keep working unchanged.
Use one endpoint per automation, subscribed to just the events it needs. A Zap that welcomes new members does not need booking or payment events, and keeping them separate means one broken automation never holds up another.
Admin > Webhooks lists every endpoint with its status, how many events it receives and how its last delivery went. Click an address to open the endpoint's page.
Send a test event
On the endpoint's page, click Send test event. ClubPal sends a webhook.test event straight away, through the same route as a real event, and it appears in the Delivery log. A test event is sent once with no retries, and a failed test never disables the endpoint.
The test body is small and fixed:
{
"id": "evt_Xb3kQ9vLmT2pWc8RfYz1Hn4d",
"object": "event",
"type": "webhook.test",
"apiVersion": "2026-10-26",
"created": "2026-11-03T18:22:05Z",
"clubId": "8d1f4c2a-6b3e-4f7a-9c5d-2e1b0a9f8c7d",
"data": {
"object": {
"object": "test",
"endpointId": "a7c3e9f1-2b4d-4e6f-8a0c-1d3e5f7a9b2c",
"message": "This is a test event from ClubPal."
},
"previous": null
}
}
The tools in the recipes below often need a real example of an event to learn its fields. For that, make a real change in ClubPal that raises the event you subscribed to, for example a test booking on a session, rather than relying on the test event.
Events
Event names stay in English in every language, because they are what the receiving service filters on. They never change once released.
Members
| Event | Sent when |
|---|---|
member.created | Someone was added to the club: they registered, an admin added them, they accepted an invitation, or they were imported. |
member.updated | A person's details changed. previous holds the old values. A change to something the payload does not include sends nothing. |
member.deleted | A person was deleted from the club. The payload holds only their id and email and "deleted": true, so you can find and remove them in the other service. Nothing else about them is sent. |
Memberships
| Event | Sent when |
|---|---|
membership.activated | A membership started, including someone rejoining after it lapsed. A member who renews by hand at checkout also sends this. |
membership.pending_approval | A membership is waiting for an admin to approve it. |
membership.renewed | A membership renewed automatically. |
membership.renewal_failed | An automatic renewal could not be completed. failureCode says why: no_payment_method (no saved card to charge), package_unavailable (the membership type no longer exists or no longer allows renewal), payment_declined (the card was declined) or other (anything else, such as a payment the card provider refused for a reason other than a decline, a card payment that needed the member to approve it, which cannot happen during an automatic renewal, or a renewal that could not be finished after the card was charged). If the payment may still go through, for example because the payment provider did not answer in time or is still processing it, nothing is sent: when it goes through, the renewal completes and sends membership.renewed instead. |
membership.expired | A membership ended, including one an admin cancelled and a trial that ran out. A membership replaced by its own renewal does not send this. A trial is usually set on the member rather than as a membership, so its event has trial: true, no id and no membership type; every other membership event has trial: false. |
Session bookings
| Event | Sent when |
|---|---|
session_booking.confirmed | A place on a session was booked. |
session_booking.waitlisted | Someone joined a session's waiting list, including when an admin lowers a session's capacity and places move back onto the waiting list. |
session_booking.promoted | Someone moved from a waiting list into a place, including when someone on the waiting list is checked in. |
session_booking.cancelled | A booking was cancelled, by the member, by an admin, or because the session was cancelled (one event per booking). |
session_booking.swapped | A booking was moved to another date or session. The payload is the new booking, and previous holds the old session's details. |
session_booking.attended | Attendance was recorded. attended is true when someone is checked in and false when a check-in is undone. Once a session has been over for 2 hours and nobody has been checked in for 2 hours, anyone booked but still not checked in gets false, as long as at least one person was checked in. |
Booking events are about participants' places only. Coaches' places on a session never send a booking event.
Only a coach or an administrator can check people in, so session_booking.attended always comes from someone running the session. Checking in a walk-in who had not booked sends session_booking.confirmed and then session_booking.attended. If the session is already full, the walk-in is added to the waiting list rather than taking a place, so it sends session_booking.waitlisted and then session_booking.attended. Checking in someone already on the waiting list sends session_booking.promoted.
Timetable
| Event | Sent when |
|---|---|
session.created | A session was added to the timetable. |
session.updated | A session, or one date of a recurring session, changed. occurrenceDate is set when only one date changed. |
session.cancelled | A session, or one of its dates, was cancelled. occurrenceDate says which date. Shortening a recurring session, or turning it into a one-off, sends one for each date it no longer runs on in the next 12 months. Extending a recurring session does not send an event for each new date. |
Payments
| Event | Sent when |
|---|---|
payment.succeeded | A payment was received, including offline payments an admin records. paymentMethod says which. Payments of zero send nothing. |
payment.refunded | A payment was refunded, fully or in part. One event per refund. The payment's refunds list holds every refund so far, each with its own id starting rf_, so you can tell which ones you have already handled. |
Payment requests
| Event | Sent when |
|---|---|
payment_request.created | A payment request was sent to someone, by an admin or automatically (an instalment or a hire balance, for example). |
payment_request.overdue | A payment request is past its due date and still unpaid. Sent once per due date. |
When a payment request has been cancelled, its cancellationReason is a code, not a sentence: membership_ended, booking_cancelled, booking_paid, member_deleted, membership_rejected, membership_cancelled_by_admin, or cancelled_by_admin when an admin cancelled it by hand.
Shop orders
| Event | Sent when |
|---|---|
shop_order.placed | A shop order was placed. |
shop_order.updated | A shop order changed status or was cancelled. |
Member passes
| Event | Sent when |
|---|---|
member_pass.purchased | A pass was bought, renewed automatically, or given to someone by an admin. |
member_pass.expired | A pass reached its expiry date, or an admin expired it. A pass whose sessions are used up has not expired and does not send this. |
Event tickets
| Event | Sent when |
|---|---|
event_ticket_order.confirmed | Tickets for an event were bought. |
Facility hire
| Event | Sent when |
|---|---|
facility_booking.confirmed | A hire booking was confirmed, by the hirer or by an admin. |
facility_booking.cancelled | A confirmed hire booking was cancelled. A booking abandoned at checkout, before it was ever confirmed, sends nothing. |
Blog
| Event | Sent when |
|---|---|
blog_post.published | A blog post went live. Saving a post so that it goes live sends this straight away, as long as its publish date is within the last 30 days, so a draft started a few weeks ago still announces but an old or back-dated post does not. A post scheduled for later is sent within about 15 minutes of its publish time, and so are the posts of a blog that has just been published, but these are only sent when their publish date is within the last 2 days, so publishing a blog full of older posts does not cross-post them all. Each post is sent once, even if it is unpublished and published again. membersOnly is true when only signed-in members can read it. |
What an event looks like
Every event arrives as a JSON POST with the same outer shape, the envelope:
| Field | What it holds |
|---|---|
id | The event's unique id, starting evt_. The same event always has the same id, including on retries. |
object | Always "event". |
type | The event name, such as session_booking.cancelled. |
apiVersion | The payload version the endpoint is pinned to, such as 2026-10-26. |
created | When the event happened, in UTC. |
clubId | Your club's id in ClubPal. |
data.object | The record the event is about, as it is now: a member, membership, session booking, session, payment, payment request, shop order, member pass, event ticket order, hire booking or blog post. Its own object field says which, and its lastUpdated says when it was last saved, in UTC. |
data.previous | On updates and status changes, the fields that changed, with their old values. null otherwise. lastUpdated never appears here. |
A few rules hold throughout:
- Field names are camelCase.
- Times ending in
Zare UTC, in the form2026-11-03T18:22:05Z. Where your club thinks in local time (session times, hire bookings, blog publish dates) the payload also gives the local date and time, in fields such assessionStartTime, a session'sstartDate,startTime,endDateandendTime, or a post'spublishDateLocal. - Money is a plain number in pounds, euros or whichever currency your club uses (
6.50, not650), with the currency's code alongside it, such as"currency": "GBP". - Choices and statuses are lower case with underscores, such as
waiting_listorpending_approval.
Sample: session_booking.cancelled
{
"id": "evt_Kq3vT9xLmB2pWc8RfYz1Hn4d",
"object": "event",
"type": "session_booking.cancelled",
"apiVersion": "2026-10-26",
"created": "2026-07-12T08:00:00Z",
"clubId": "8d1f4c2a-6b3e-4f7a-9c5d-2e1b0a9f8c7d",
"data": {
"object": {
"object": "session_booking",
"id": "bk-1",
"memberId": "mem-1",
"name": "Sam Taylor",
"sessionId": "ses-1",
"sessionName": "Evening Row",
"sessionDate": "2026-07-14",
"sessionStartDateTime": "2026-07-14T18:00:00Z",
"sessionEndDateTime": "2026-07-14T19:30:00Z",
"sessionStartTime": "19:00",
"sessionEndTime": "20:30",
"bookingStatus": "cancelled",
"attended": null,
"hasBeenWaitlisted": false,
"cost": 6.50,
"amountPaid": 6.50,
"currency": "GBP",
"isPaid": true,
"paymentMethod": "when_booking",
"dateAdded": "2026-07-10T12:01:02Z",
"dateCancelled": "2026-07-12T08:00:00Z",
"membershipStatus": "active",
"paymentId": "rcpt-1",
"memberPassId": null,
"lastUpdated": "2026-07-12T08:00:00Z"
},
"previous": {
"bookingStatus": "confirmed",
"dateCancelled": null
}
}
}
Sample: member.created
{
"id": "evt_Nw0f9eD7cB5a4L93827zQy",
"object": "event",
"type": "member.created",
"apiVersion": "2026-10-26",
"created": "2026-03-02T10:15:30Z",
"clubId": "8d1f4c2a-6b3e-4f7a-9c5d-2e1b0a9f8c7d",
"data": {
"object": {
"object": "member",
"id": "mem-1",
"name": "Sam Taylor",
"firstName": "Sam",
"lastName": "Taylor",
"email": "sam@example.com",
"status": "pending_approval",
"membershipTypeId": "pkg-1",
"membershipTypeName": "Senior",
"membershipStarts": "2026-04-01",
"membershipExpires": "2027-03-31",
"autoRenew": true,
"groups": ["grp-1", "grp-2"],
"gender": "non_binary",
"dateOfBirth": "1990-05-17",
"phone": "01234 567890",
"mobile": "07700 900123",
"city": "Henley",
"postCode": "RG9 1AA",
"joined": "2026-03-02T10:15:30Z",
"consentToContact": true,
"enableEmail": false,
"isManaged": true,
"lastUpdated": "2026-03-02T10:15:30Z"
},
"previous": null
}
}
Sample: member.deleted
A deleted person's event carries only what you need to find them in the other service. Its lastUpdated is the time of the deletion.
{
"id": "evt_Pm7nR2sTq4vW8xYz1aBc3dEf",
"object": "event",
"type": "member.deleted",
"apiVersion": "2026-10-26",
"created": "2026-09-01T14:02:11Z",
"clubId": "8d1f4c2a-6b3e-4f7a-9c5d-2e1b0a9f8c7d",
"data": {
"object": {
"object": "member",
"id": "mem-1",
"email": "sam@example.com",
"deleted": true,
"lastUpdated": "2026-09-01T14:02:11Z"
},
"previous": null
}
}
When a person is deleted, ClubPal also deletes every stored event about them, such as their bookings, their memberships or a payment request sent to them, together with its deliveries. Those entries disappear from the delivery log and cannot be resent. The member.deleted event itself is kept. Events about other people stay: a payment request an admin sent names that admin only by id (requestedByMemberId), so deleting the admin leaves it alone. Event ticket holders are stored as names only, so a ticket order someone else bought that lists the deleted person as a ticket holder keeps their name until the event expires after 30 days.
Sample: payment.refunded
{
"id": "evt_Hj5kL7mN9pQ2rS4tU6vW8xY0",
"object": "event",
"type": "payment.refunded",
"apiVersion": "2026-10-26",
"created": "2026-07-15T10:00:00Z",
"clubId": "8d1f4c2a-6b3e-4f7a-9c5d-2e1b0a9f8c7d",
"data": {
"object": {
"object": "payment",
"id": "rcpt-1",
"memberId": "mem-1",
"name": "Sam Taylor",
"email": "sam@example.com",
"description": "Main Hall hire",
"paymentDateTime": "2026-07-14T09:29:12Z",
"amount": 120.00,
"currency": "GBP",
"category": "facility_hire",
"entityId": "fb-1",
"paymentMethod": "Card",
"refundedAmount": 20.00,
"status": "partially_refunded",
"refunds": [
{
"id": "rf_fTT4QjIz2Sz5n5lH",
"amount": 20.00,
"date": "2026-07-15T10:00:00Z"
}
],
"lastUpdated": "2026-07-15T10:00:00Z"
},
"previous": null
}
}
Sample: payment_request.overdue
A payment request names the person it is for (memberId and name). Whoever sent it appears only by id, as requestedByMemberId, never by name.
{
"id": "evt_Rt6uV8wX0yZ2aB4cD6eF8gH0",
"object": "event",
"type": "payment_request.overdue",
"apiVersion": "2026-10-26",
"created": "2026-07-14T09:30:00Z",
"clubId": "8d1f4c2a-6b3e-4f7a-9c5d-2e1b0a9f8c7d",
"data": {
"object": {
"object": "payment_request",
"id": "pr-1",
"memberId": "mem-1",
"name": "Sam Taylor",
"description": "Balance for court hire",
"note": "Pay by Friday please.",
"amount": 45.00,
"currency": "GBP",
"status": "outstanding",
"dueDate": "2026-07-10",
"overdue": true,
"origin": "automatic",
"subject": "facility_booking",
"subjectId": "fb-1",
"batchId": "batch-1",
"requestedByMemberId": "admin-1",
"dateCreated": "2026-07-01T11:00:00Z",
"datePaid": null,
"paymentId": null,
"paidOffline": false,
"dateCancelled": null,
"cancellationReason": null,
"reminderCount": 1,
"lastRemindedDateTime": "2026-07-08T09:00:00Z",
"autoCharge": true,
"chargeFailed": true,
"attemptCount": 2,
"lastAttemptAt": "2026-07-11T02:00:00Z",
"nextAttemptAt": "2026-07-15T02:00:00Z",
"lastUpdated": "2026-07-14T09:29:45Z"
},
"previous": null
}
}
The ids above are shortened for reading. Real ids are longer, but treat every id as an opaque string.
The fastest way to see the exact fields of any other event is to subscribe to it, cause it in ClubPal, and open the delivery in the Delivery log, which shows the full Request body.
Headers
Every request carries:
| Header | Value |
|---|---|
Content-Type | application/json |
User-Agent | ClubPal-Webhooks/1.0 |
ClubPal-Event-Id | The event's id, the same as in the body. |
ClubPal-Event-Type | The event name, the same as type in the body. |
ClubPal-Version | The payload version, the same as apiVersion in the body. |
ClubPal-Signature | t=<time>,v1=<signature>, explained below. |
Check that a request came from ClubPal
Anyone who knows your endpoint's address could send it a request. If you write your own receiver, check the ClubPal-Signature header on every request before trusting it.
The header looks like this:
ClubPal-Signature: t=1700000000,v1=35495024f4ef3f94e5a93e22221544c4b75e9a42300cd965ab81cb85cd994e91
tis when ClubPal sent the request, in seconds since 1 January 1970 (UTC).v1is a hex HMAC-SHA256 signature. To check it, taket, a full stop and the raw request body exactly as received, compute the HMAC-SHA256 of that text using the whole signing secret (includingwhsec_) as the key, and compare the result tov1.- For 24 hours after you roll a secret, the header carries two
v1=values, one for the new secret and one for the old. Accept the request if any of them matches.
Then:
- Reject requests older than 5 minutes. Compare
twith your own clock and refuse anything more than 300 seconds out. This stops someone replaying a request they captured. A retry is signed afresh, so a genuine retry always passes. - De-duplicate by event id. A retry resends the same event with the same
id, and an event can occasionally arrive twice. Store the ids you have handled and ignore one you have seen before. - Do not rely on the order events arrive in. A retry can arrive after a later event about the same record. Every record in
data.objectcarrieslastUpdated, the time it was last saved: keep the latestlastUpdatedyou have applied for each record, and ignore an event whoselastUpdatedis older than that, because it describes the record as it was before a change you already have.
Verify against the raw body. If your framework parses the JSON first and you turn it back into text, the spacing changes and the signature will not match.
Node.js
const crypto = require("crypto");
function verifyClubPalSignature(rawBody, header, secret) {
let timestamp = null;
const signatures = [];
for (const part of header.split(",")) {
const i = part.indexOf("=");
const key = part.slice(0, i), value = part.slice(i + 1);
if (key === "t") timestamp = value;
if (key === "v1") signatures.push(value);
}
if (!timestamp) return false;
// Refuse anything signed more than 5 minutes ago (or ahead).
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
if (!(age <= 300)) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.`)
.update(rawBody)
.digest();
return signatures.some((sig) => {
const given = Buffer.from(sig, "hex");
return given.length === expected.length && crypto.timingSafeEqual(given, expected);
});
}
With Express, keep the raw body by using express.raw on the webhook route:
app.post("/clubpal", express.raw({ type: "application/json" }), (req, res) => {
if (!verifyClubPalSignature(req.body, req.get("ClubPal-Signature") || "", process.env.CLUBPAL_WEBHOOK_SECRET)) {
return res.sendStatus(400);
}
const event = JSON.parse(req.body);
// Ignore event.id if you have already handled it, then do the work.
res.sendStatus(200);
});
Python
import hashlib
import hmac
import time
def verify_clubpal_signature(raw_body: bytes, header: str, secret: str) -> bool:
timestamp, signatures = None, []
for part in header.split(","):
key, _, value = part.partition("=")
if key == "t":
timestamp = value
elif key == "v1":
signatures.append(value)
if timestamp is None or not timestamp.isdigit():
return False
# Refuse anything signed more than 5 minutes ago (or ahead).
if abs(time.time() - int(timestamp)) > 300:
return False
expected = hmac.new(
secret.encode("utf-8"),
timestamp.encode("utf-8") + b"." + raw_body,
hashlib.sha256,
).hexdigest()
return any(hmac.compare_digest(expected, sig) for sig in signatures)
In Flask, pass request.get_data() as raw_body.
To check your own code, the secret whsec_test, the time 1700000000 and the body {} give the signature 35495024f4ef3f94e5a93e22221544c4b75e9a42300cd965ab81cb85cd994e91.
Responding, retries and failures
Reply with any 2xx status within 10 seconds to tell ClubPal the event arrived. If there is slow work to do, reply first and do it afterwards. ClubPal does not follow redirects: a 3xx reply counts as a failure, so give ClubPal the final address.
If a delivery fails (an error status, a redirect, no answer within 10 seconds, or no connection at all), ClubPal tries again: after 1 minute, 5 minutes, 30 minutes, 2 hours, 5 hours and then every 10 hours. That is 12 attempts over about 3 days. Each retry sends the same body and the same event id, with a fresh t and signature. If something on ClubPal's side holds a delivery up before it is tried, ClubPal picks it up again within about 15 minutes, so an event is never silently left behind. A delivery still waiting 4 days after the event (a test event, an hour after it was sent) is given up on and marked Failed; that never disables the endpoint or emails anyone.
If ClubPal cannot send a delivery because of a problem on its own side, the attempt shows Not sent: a problem on ClubPal's side in the delivery log and is retried on the same schedule. If every attempt fails that way, the delivery is marked Failed, but the endpoint stays on and nobody is emailed, because nothing is wrong at your end. If at least half of a delivery's attempts failed at your end, though (an error status, a timeout or no connection), the delivery counts as failing at your end, and it can disable the endpoint as described below even if its last attempt failed on ClubPal's side.
What disables an endpoint. If an event is still not delivered after its last attempt, and nothing else has been delivered to that endpoint since that event was first tried, ClubPal stops sending to it. An endpoint that rejects one particular event but keeps accepting others stays on: that event is marked Failed in the delivery log, and the rest keep flowing.
When an endpoint is turned off, its status changes to Disabled after failures, and every System Administrator gets one email, Your Webhook Endpoint Has Been Turned Off, saying that an event could not be delivered to it for about three days and nothing else got through in that time, with a View Endpoint button. The email is sent even if your club has turned off ClubPal's emails. New events are not sent to the endpoint, and other deliveries still waiting for it are marked Skipped (shown as Stopped: endpoint turned off or deleted, or event no longer stored; some of them may have been tried before).
Re-enabling. Fix the problem at the other service, then go to Admin > Webhooks, open the endpoint and click Enable. Events that happened while it was disabled are not sent automatically. To catch up, use Resend on the entries you need in the delivery log.
You can also switch an endpoint off yourself with Disable, for example while you rebuild an automation, and back on with Enable. Nothing is sent to a disabled endpoint, and nothing that happens meanwhile is queued for it.
The delivery log
The endpoint's page shows the Delivery log: everything sent to that endpoint in the last 30 days, newest first. Filter it by Status (Pending, Delivered, Failed or Skipped) or by Event type, and open a row to see the Request body and every attempt: when it was made, the reply status or the error (such as Timed out or Could not connect), how long it took and the start of the reply.
Resend sends the same event to the endpoint again as a new delivery. It keeps the same event id, so a receiver that de-duplicates will ignore it if it already handled the event: resend is for events that never arrived, not for running an automation twice. The endpoint has to be enabled to resend. An event older than 30 days is no longer stored and cannot be resent.
Rolling the secret
If a secret may have leaked, or someone who knew it has left, click Roll secret on the endpoint's page and confirm. ClubPal shows the New signing secret once, with a note saying until when the old secret keeps working; copy it, then click I have copied it. Requests are signed with both the new and old secrets for the next 24 hours, so you have a day to update your receiver before the old one stops working.
If you roll again within those 24 hours, for example because you lost the new secret before saving it, the secret made by the last roll stops working at once and is replaced by another new one. The secret from before you started rolling keeps working until its original 24 hours are up, and the note with the new secret says exactly when. So a receiver that still has the original secret keeps working while you sort it out.
Deleting an endpoint
On the endpoint's page, click Delete. ClubPal asks twice: first Delete this endpoint?, explaining that nothing more is sent to it, including retries still waiting, and that the other service keeps what it already received; then Delete the endpoint for good? This cannot be undone. Click Yes both times.
To stop sending for a while without losing the endpoint and its delivery log, use Disable instead.
Recipes for Zapier, Make and Power Automate
Zapier, Make and Power Automate all accept webhooks without any code. They generally cannot check the ClubPal-Signature header, so the address they give you is the only thing protecting the automation. Treat it like a password: do not post it in a chat, a forum or a screenshot, and if it does get out, create a new trigger with a new address, update the endpoint in ClubPal, and delete the old one.
Zapier
- Create a Zap. For the trigger, choose Webhooks by Zapier and the event Catch Hook. Webhooks by Zapier is one of Zapier's premium apps, so you may need a paid Zapier plan.
- Copy the webhook URL Zapier shows.
- In ClubPal, add an endpoint with that URL and the events the Zap needs.
- Make the event happen in ClubPal (for
member.created, add a test member), then in Zapier click Test trigger. Zapier pulls in the event and lists its fields, such as Data Object Email and Data Object First Name. - Add the actions you want, using those fields.
If one endpoint sends several kinds of event to one Zap, add a Filter by Zapier step on Type so each action only runs for the events it is meant for.
Make
- Create a scenario. Add the Webhooks module and choose Custom webhook.
- Click Add, give the webhook a name, and save. Copy the address Make shows.
- In ClubPal, add an endpoint with that address and the events the scenario needs.
- In Make, the module waits to learn the data structure. Make the event happen in ClubPal and Make reads the fields from it. If it is no longer listening, click Redetermine data structure and cause the event again.
- Add the modules you want, using the fields under
data›object.
Use a filter on type between modules when one webhook receives several kinds of event.
Power Automate
- Create an Automated cloud flow (or an instant flow) with the trigger When an HTTP request is received. This is a premium trigger, so it needs a Power Automate licence that includes premium connectors.
- Set Who can trigger the flow? to Anyone, because ClubPal does not sign in to Microsoft.
- Under Request Body JSON Schema, click Use sample payload to generate schema and paste one of the samples above, or a real request body copied from the ClubPal delivery log.
- Save the flow. Power Automate then shows the HTTP URL. Copy it.
- In ClubPal, add an endpoint with that URL and the events the flow needs.
- Add the actions you want, using the fields from the schema.
The Power Automate URL contains its own access key (sig= in the address), which is why it must be kept private.
Tips
- If you are writing your own receiver, record the
ClubPal-Event-Idbefore you start the work, so a retry arriving while the first attempt is still running is spotted as a duplicate. - When something does not arrive, the Delivery log is the first place to look: the reply status and the start of the reply usually say exactly what went wrong.
- Changes made by ClubPal support staff, such as correcting a member's details at your request, send events just like changes your own admins make.
- Events are kept for 30 days. After that they leave the delivery log and cannot be resent.
- If the endpoint changes while you are editing it, for example because another administrator edits, disables or rolls it, your save is refused with This endpoint changed after you opened it. Open it again to see the changes. Open it again and redo your change, so one change never silently undoes another. This also happens if you changed it yourself in another tab. If it was deleted meanwhile, ClubPal says it could not be saved.
See also
- Permissions: what the System Administrator permission grants
- Connect an AI assistant: the other way to use your club's data outside ClubPal