POST request with the event data to your URL.
200, 201 or 202 (response timeout: 15 seconds).How it works
The event is recorded
PaymentCompleted) with a timestamp and the initiator parameters.Active endpoints are looked up
The payload is assembled
data,
a single idempotencyKey and timestamp are built. Extra fields are stripped according to a strict schema (allow-list).Signed delivery and retries
POST request with a signature header (HMAC-SHA256). If the receiver does not respond with
200/201/202, delivery is retried with an increasing delay, up to 5 attempts in total.active = false or delete it.Message format
The request body is a JSON object with the following structure:Signature verification
Every request carries asignature header: an HMAC-SHA256 (hex) of the raw request body,
signed with your endpoint’s secret key.
secretKey is passed to HMAC as is, as a 64-character ASCII/UTF-8 string. Despite its
appearance, you do not need to decode it from hex or base64. The resulting signature is a 64-character
lowercase hex string with no sha256= prefix.Delivery and retries
200/201/202, as well as a timeout or network error, counts as a failure and triggers
a retry. After 5 failed attempts the event is marked as failed and is no longer sent.Managing endpoints
Webhook endpoints are configured in the account: Manage → School → Webhooks (/manage/school/webhooks).
You need the School Settings Management permission (SchoolManageSettings). Endpoints cannot be managed through the SaaS API.
For each endpoint you set:
url— the receiver address (up to 255 characters); use HTTPS;events— the list of events it is subscribed to;active— the activity flag (trueby default);note— a free-form note (up to 255 characters);secretKey— the secret for signature verification, generated automatically when the endpoint is created.
secretKey is created together with the endpoint. In the account
you copy it with the “Copy signature” command, available in the endpoint row menu or next to the URL field in its form
(despite the name, it copies the secret itself, not a ready-made signature). Store it in your service’s environment
variables (EXODE_WEBHOOK_SECRET in the examples above). If the secret is not displayed, request it from
support.Test delivery
A test delivery from the form of a saved endpoint is signed with that endpoint’s ownsecretKey. The body format,
algorithm and header are exactly the same as for production events, so your receiver does not need a separate
verification branch for test webhooks.
Supported events
Below are the events that are actually delivered, along with the structure of theirdata field. Nested entities
(user, profile, course, payment, etc.) are described in the object reference.
UserSignedUp — user sign-up
UserSignedUp — user sign-up
data:user— the user object.profile— the profile card (nullif not filled in).states.utmSignupParams— an object with the UTM tags of the first visit (optional).
UserAcquainted — onboarding completed
UserAcquainted — onboarding completed
data structure is identical to UserSignedUp
(user, profile, states.utmSignupParams).UserTgConnected — Telegram linked
UserTgConnected — Telegram linked
data:user— the user object (with the currenttgId).profile— the profile card (optional).prevTgId— the previoustgId, for tracking re-linking (number | null).
UserCreatedViaLms — user created by the school
UserCreatedViaLms — user created by the school
user/create and user/upsert
(only when upsert creates the user rather than updating an existing one). A user who signs up on their own
arrives as a separate UserSignedUp event.data:user— the user object.profile— the profile card (optional).
CourseProgressChanged — lesson progress changed
CourseProgressChanged — lesson progress changed
data:user— the user with profile.course— the course object.product— the course product (optional).access— the user’s access to the course product (optional).states.utmEnrollParams,states.utmSignupParams— enrollment and sign-up UTM tags; see UTM attribution.groups— an array of the user’s groups in the course (optional).status— the new lesson status from the progress record (optional):NotStarted,OnTheory,OnPractice,OnReview,OnCorrectionorCompleted; seecourseProgressfor details.lessonId— the lesson ID (optional).
CourseCompleted — course completed
CourseCompleted — course completed
data:user— the user with profile.course— the course object.product— the course product (optional).access— the user’s access (optional).states.utmEnrollParams,states.utmSignupParams— enrollment and sign-up UTM tags; see UTM attribution.groups— an array of groups (optional).
CourseLessonPracticeCompleted — practice completed
CourseLessonPracticeCompleted — practice completed
data:user— the user with profile.course— the course (optional).lesson— the lesson (optional).practice— the practice parameters (optional).attempt— the attempt, with score and status (optional).variantId— the variant ID (optional).
PaymentCompleted — payment succeeded
PaymentCompleted — payment succeeded
data.payment — the payment object with the full tree: invoice
(the invoice), invoice.user (the buyer with profile and school), invoice.products (line items with product/course,
price and discount), acquiring (the acquiring and its provider, without secrets). Monetary fields are numbers.The event is sent only when money is actually charged: initializing a recurring payment (card binding,
a zero payment with the BindingCompleted status) does not trigger the webhook.ProductEnrolledToFree — free access granted
ProductEnrolledToFree — free access granted
data:user— the user.profile— the profile card (optional).access— the access object (optional).states.utmEnrollParams,states.utmSignupParams— enrollment and sign-up UTM tags; see UTM attribution.product— the product (optional).course— the course (optional).
ProductEnrolledViaLms — access granted manually (LMS)
ProductEnrolledViaLms — access granted manually (LMS)
data structure is identical to ProductEnrolledToFree (user, profile, access, product, course, states).ProductEnrolledViaPayment — access granted after payment
ProductEnrolledViaPayment — access granted after payment
data structure is identical to ProductEnrolledToFree (user, profile, access, product, course, states).ProductEnrolledByInviteLink — enrollment via invite link
ProductEnrolledByInviteLink — enrollment via invite link
data: the same fields as ProductEnrolledToFree (user, profile, access, product, course, states),
plus:inviteLinkId— the ID of the link the user came through (duplicated inaccess.meta.inviteLinkId).
CertificateIssued — certificate issued
CertificateIssued — certificate issued
data:user— the user with profile.course— the course object.product— the course product (optional).certificate— the certificate object with a publiclink(opens without authorization), a validity period and a snapshot of the data at the time of issue.
UserSignedIn, UserLoggedOut, UserJoinedByReferral,
CourseLessonPracticeDetailedSent, CourseLessonPracticeAutoVerifySent, ProductRefundCompleted,
ProductAccessSubscriptionEnding7Days and ProductAccessSubscriptionEnding1Day, but
the formal data contract for them is not fixed yet, so the payload contents are not guaranteed.
Check with support before using them.UTM attribution
UTM tags arrive in three places, depending on the event:utm_source, utm_medium, utm_campaign, utm_term, utm_content, gclid, fbclid, yclid,
referrer, aff_id, sub_id, track_id (only the ones that were passed are present). If the user arrived without tags, the object
is absent or empty.
Example: PaymentCompleted
The body below is a real test-delivery example (synthetic values, exact field set):Troubleshooting
Retries
Retries
idempotencyKey. Delays are counted from the previous
failed attempt (≈ 11 / 33 / 77 / 165 minutes). Return a success code to stop the retries.Temporary disabling
Temporary disabling
active = false to pause delivery without losing the configuration and the secret key.
Retries of events that were queued before disabling stop after the next failed attempt.Logging
Logging
event, timestamp, idempotencyKey and the signature verification result: this speeds up reconciliation with
our side when investigating incidents.Updated: 2026-09-28 05:04 UTC