This guide is for course providers and their developers who want Cademy to tell another system automatically when something happens in Cademy. A webhook sends information from Cademy to another system when selected events happen. For example, Cademy can notify your CRM when a registration is created, or your reporting system when an order is updated.
Webhooks are useful for custom integrations because the other system does not need to keep checking Cademy for changes.
A System Admin creates the webhook in Cademy. A developer builds the system that receives it and provides its web address, called the endpoint URL.
Important: A webhook is a developer tool. If you do not already have an endpoint URL, ask your developer or integration provider to create one. Your normal website address is not a webhook endpoint unless a developer has built it to receive Cademy webhooks.
To connect other apps through Zapier instead, see Connect Other Apps with Zapier. To call the Cademy API from your own system instead, see Create and Manage API Keys.
What You Need to Use Webhooks (Plan and Access)
You need:
A Teams or Enterprise plan. On other plans the Webhooks page is locked.
System Admin access. Only System Admins can open and manage Settings > Webhooks.
A public HTTPS endpoint that can accept JSON
POSTrequests.Access to the system receiving the requests so you can store the secret and verify each request.
What Should I Enter in the Webhook Endpoint URL?
Endpoint URL is the web address where Cademy will send webhook messages. It is mandatory and cannot be left blank.
Your developer or integration provider should give you this address. For example:
https://example.com/webhooks/cademy
Do not enter your normal website homepage unless it has been specifically built to receive Cademy webhook requests.
The URL must:
Start with
https://.Be available publicly on the internet.
Not use localhost, a
.localaddress, or a private or reserved IP address.Return a successful
2xxresponse within 10 seconds.
When you save, Cademy checks the format of the address and that its host name resolves to a public address. It does not send a test request, so an address that passes these checks can still fail when the first event is sent.
Create a Webhook in Cademy
From the Cademy admin dashboard, go to Settings > Webhooks.
Select Add Webhook. The New Webhook window opens.
Enter the address provided by your developer or integration provider in Endpoint URL.
Use Description (optional) to explain what the webhook connects to. You can leave this blank.
Under Events, select every event you want Cademy to send. You must select at least one. Selecting a group name, such as Courses, selects every event in that group.
Select Create. The button cannot be selected until there is an address and at least one event.
Copy the Webhook Secret
After you select Create, Webhook Secret opens.
Select the copy icon beside the secret. Cademy shows Webhook secret copied to clipboard.
Store the secret securely in the system that receives the webhook.
Select Done. The webhook appears in the list on the Webhooks page with its address, the number of events and the status Enabled.
The receiving system uses this secret to confirm that requests came from Cademy. Give it only to the person who sets up the receiving system. Do not publish it. You can open the webhook menu and use Reveal Secret… if you need to copy it again later.
The secret starts with whsec_. Use the whole value, including that prefix, when you verify a signature.
Send a Test Webhook (Trigger a Real Event)
Cademy has no test button. To check the webhook, make one of its selected events happen in Cademy. Then go to Settings > Webhooks, open the menu beside the webhook and select View Deliveries… to see the attempt. A delivery that worked has a label such as HTTP 200.
Choose Which Events the Webhook Sends
You can select one event, several events, or every event. To change the selection later, use Edit… in the webhook menu.
Group | Event shown in Cademy | Event code sent |
Courses | Course created |
|
Courses | Course updated |
|
Courses | Course deleted |
|
Course Dates | Course date created |
|
Course Dates | Course date updated |
|
Course Dates | Course date deleted |
|
Contacts | Contact created |
|
Contacts | Contact updated |
|
Contacts | Contact deleted |
|
Registrations | Registration created |
|
Registrations | Registration cancelled |
|
Orders | Order created |
|
Orders | Order updated |
|
Course updated can be sent for published, private, and draft courses, including when a course’s visibility changes. It is also sent when the course’s dates are added, changed or removed. The course information sent does not include the course’s visibility.
Some changes arrive under a different event from the one you might expect:
Deleting a course sends Course date deleted for each of its dates and Course deleted for the course. They can arrive in any order.
Order created is also sent for a draft order.
An order that is cancelled, approved, paid or refunded arrives as Order updated. There is no separate event for these.
Registrations have two events only, created and cancelled. Check
statusin the data: a registration can be created as pending, and a later change to active does not send a registration event.
What a Cademy Webhook Request Contains
This section is for the developer who builds the receiving endpoint.
Cademy sends a signed HTTPS POST request shortly after a selected event happens.
Webhook JSON Body and Event ID
Every request uses the same top-level structure:
{
"id": "evt_3f9a1c0b7d2e4a56b8c9d0e1f2a3b4c5",
"type": "registration.created",
"created": "2026-07-17T10:00:00.000Z",
"data": {
"id": "registration-id"
}
}data is shortened in this example. In a real registration.created request it has more fields, such as status, course_id, course_date_id and student_ref.
Field | Meaning |
| The event ID: |
| The event code, such as |
| The date and time the event happened, in ISO 8601 format. |
| Information about the course, course date, contact, registration, or order. |
The record’s own ID is data.id for courses, contacts and registrations, and in every deletion event. For course_date.created, course_date.updated, order.created and order.updated it is data._id, with an underscore.
A registration’s data identifies the learner by student_ref. It does not contain the learner’s name or email address. A contact’s data carries the same student_ref with the name and email address.
For course_date.created and course_date.updated, data.currency gives the currency that applies to the date: its block’s currency for a session, its own currency when set, otherwise the course currency. It can differ from the currency in course data. Order data has the order’s saved currency; use that for order amounts instead of looking up the course’s current currency. Currency codes describe amounts; the webhook does not perform exchange-rate conversion.
Deletion events contain identifiers instead of the full deleted record. course.deleted and contact.deleted contain id. course_date.deleted contains id (the course date) and course_id.
Webhook Request Headers
Header | Meaning |
| The event code. |
| A unique ID for that delivery attempt. |
| The Unix timestamp, in seconds, used to sign the request. |
| The HMAC SHA-256 signature. |
|
|
|
|
Each retry has a new delivery ID, timestamp and signature. The JSON body and its id stay the same.
Verify the Cademy Webhook Signature (HMAC SHA-256)
Verify every request before using its data:
Read the raw request body exactly as Cademy sent it. Do this before parsing or reformatting the JSON.
Read
X-Cademy-Timestamp.Join the timestamp, a full stop, and the raw body:
{timestamp}.{rawBody}.Create an HMAC SHA-256 digest using the webhook secret.
Add the
sha256=prefix and compare the result withX-Cademy-Signatureusing a timing-safe comparison.
Node.js example. In it, secret is the webhook secret, timestamp is the X-Cademy-Timestamp header value, signature is the X-Cademy-Signature header value, and rawBody is the raw request body as text:
import { createHmac, timingSafeEqual } from 'node:crypto';
const expected = `sha256=${createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex')}`;
const received = Buffer.from(signature ?? '');
const wanted = Buffer.from(expected);
const isValid = received.length === wanted.length && timingSafeEqual(received, wanted);Important: Webhooks use at-least-once delivery and events are not guaranteed to arrive in order. Your system must safely handle duplicates by storing the JSON body’s id and ignoring any event ID it has already processed.
Check Webhook Deliveries and Retries
View Recent Webhook Deliveries
Go to Settings > Webhooks.
Open the menu beside the webhook.
Select View Deliveries… from the menu.
Recent Deliveries lists the 50 most recent attempts, newest first, with one row for each attempt. A row shows the event code, a result label, the time, the attempt number, the duration, and any error. The result label is the response status, such as HTTP 200, or Error when no response was received, for example after a timeout. Delivery records are kept for 30 days. The JSON payload is not stored in this delivery history.
What Happens When a Webhook Delivery Fails?
Any 2xx response counts as successful. A non-2xx response, network error, unsafe redirect, or request that takes longer than 10 seconds counts as failed.
Cademy follows up to 3 redirects when they stay on the same host. A redirect to a different host counts as failed.
Cademy makes up to 5 attempts over about 30 minutes. The first attempt happens immediately, followed by up to 4 retries. The retries wait 30 seconds, 2 minutes, 10 minutes and 15 minutes after the previous attempt.
Why Was My Webhook Disabled Automatically (Auto-Disabled)?
If 10 consecutive events each use all 5 attempts without succeeding, Cademy changes the webhook’s status to Auto-disabled and shows Deliveries paused under it. This prevents repeated requests to an endpoint that is not working.
Fix the endpoint, then go to Settings > Webhooks and select Re-enable. A successful delivery resets the consecutive failure count.
Edit, Disable or Delete a Webhook
Go to Settings > Webhooks and open the menu beside a webhook.
Option | What it does |
Edit… | Change the Endpoint URL, Description (optional), or Events. |
View Deliveries… | Open the delivery history from the last 30 days. |
Reveal Secret… | Show and copy the current webhook secret. |
Regenerate Secret… | Create a new secret, after you confirm. The current secret stops working immediately, so update your receiving system straight away. |
Disable | Stop new deliveries without deleting the webhook. Retries that come due while the webhook is disabled are dropped. Shown when the webhook is enabled. |
Enable | Start sending selected events again. Shown when the webhook is disabled or auto-disabled. |
Delete… | Permanently remove the webhook and stop deliveries immediately, after you confirm. |
Events that happen while a webhook is disabled are not sent after you enable it. Retries that come due while the webhook is disabled are dropped and are not sent later.
Fix Common Webhook Problems
Cademy Will Not Accept the Webhook Endpoint URL
Check the message shown under Endpoint URL:
Endpoint URL is not a valid URL: Enter a complete address that starts with
https://.Endpoint URL must use HTTPS: Replace
http://with a workinghttps://endpoint.Endpoint URL cannot target localhost: Use a public endpoint instead of localhost or a
.localaddress.Endpoint URL cannot target a private or reserved address: Use a public internet address instead of a private or internal IP address.
Endpoint URL resolves to a private or reserved address: The host name points to a private or internal address. Use a host name that points to a public internet address.
A message that contains
ENOTFOUND: Cademy could not find the host name in the address. Check the address for typing mistakes.
The Webhook Is Enabled but Nothing Arrives
Open Edit… and confirm the correct event is selected.
Confirm the event happened after the webhook was enabled. Cademy does not send historical events.
Open View Deliveries… and look for the event code and time.
Confirm the endpoint accepts HTTPS
POSTrequests and returns a2xxresponse within 10 seconds.Check that your endpoint is not redirecting requests to a different host.
The Webhook Signature Does Not Match
Use the raw request body before JSON parsing or reformatting.
Use the value from
X-Cademy-Timestampin the signed content.Confirm your receiving system uses the current secret, including its
whsec_prefix. If you used Regenerate Secret…, the previous secret stopped working immediately.Confirm the expected value includes the
sha256=prefix.
Frequently Asked Questions
Do I need a developer to use webhooks?
You need a receiving endpoint. A developer builds it, or an integration service gives you an endpoint URL. You create and manage the webhook in Cademy yourself.
Can I leave Endpoint URL blank?
No. Cademy needs a public HTTPS endpoint before it can create the webhook.
Can I use my website homepage as Endpoint URL?
Only if a developer has built that address to receive Cademy webhook POST requests. Cademy accepts any public HTTPS address, and if a normal homepage answers with a 2xx status, Recent Deliveries shows that HTTP status as successful even though nothing used the data.
Can I leave Description (optional) blank?
Yes. It is only a label to help System Admins understand what the webhook connects to.
Can I send a test webhook?
No, there is no separate test button. Create the webhook, then perform one of its selected events in Cademy and open View Deliveries… from the webhook menu.
Will Cademy send an event exactly once?
No. Cademy uses at-least-once delivery, so the same event can arrive more than once. Use the JSON body’s id to ignore duplicates.
Will events arrive in the same order they happened?
Not always. Your receiving system should use the event’s created value and must not rely on delivery order.
How many times will Cademy retry a failed delivery?
Up to 4 times. Cademy makes up to 5 attempts over about 30 minutes.
How long is delivery history kept?
30 days. Recent Deliveries shows the 50 most recent attempts from that period. It does not store the JSON payload.
Can I see the secret again?
Yes. Open the webhook menu and select Reveal Secret… to show and copy it.
What happens if I regenerate the secret?
The previous secret stops working immediately. Copy the new secret to your receiving system straight away.
Can I pause a webhook without deleting it?
Yes. Use Disable. This stops new deliveries, and retries that come due while the webhook is disabled are dropped. Use Enable when you are ready to start again.



