How to set up school webhooks: available events, request format, signature verification, retries and automatic endpoint disabling.
Webhooks automatically notify your external services about events in the school: sign-up, payment (commercial schools only), course enrollment, course completion. When an event happens, the platform sends a POST request with the event data to the address you specified. This is how you connect the school to CRMs, bots, spreadsheets and your own services without constantly polling the API.
Webhooks are in Control panel → My school → For developers → Webhooks (in a corporate school — Control panel → Company → For developers → Webhooks). A staff member with the School settings management permission (in a corporate school — Company settings management) can work with them.
A school can have no more than 5 webhooks. Deleted webhooks do not count toward the limit.
Each webhook is one address and a set of events it is subscribed to.
Webhooks are set up only in the control panel. You cannot manage them through the school API.
Open the Webhooks section (in a corporate school — Company webhooks) and click Create a webhook.
2
Enter the address
In the Link field, enter the address of your service, for example https://my-service.com/api/exode. The address can be up to 255 characters long. Use HTTPS.
3
Add a note
In the Service note field, write what this webhook is for, for example “AmoCRM integration”. The note is visible only in the webhook list and can be up to 255 characters long.
4
Choose the events
Select the events in the Selected events list. The counter on the right shows how many events are selected.
5
Check delivery
Click the arrow next to an event to send a test event to the specified address. Make sure your service received the request.
6
Save the webhook
Click Create. On creation, the platform generates a secret key for signature verification.
In the webhook list, each row has an Active / Inactive switch and an action menu: Edit, Copy signature and Delete webhook.
13 events are available for subscription (in a corporate school — 11):
Event in the control panel
Identifier
When it fires
The user has registered
UserSignedUp
A new user signed up for the school on their own
The user has filled in the data
UserAcquainted
The user filled in and submitted the introduction form
User connected Telegram
UserTgConnected
The user linked or changed Telegram
User created via LMS
UserCreatedViaLms
A school staff member created the user manually
Enrollment in a free course
ProductEnrolledToFree
The user enrolled in a free course
Registration for the course via LMS
ProductEnrolledViaLms
A school staff member granted access manually
User registration for the course through payment
ProductEnrolledViaPayment
Access was granted after a successful payment (commercial schools only)
Enrollment via invite link
ProductEnrolledByInviteLink
The user enrolled via an invite link
Payment completed
PaymentCompleted
The payment was successfully paid (commercial schools only)
Course progress updated
CourseProgressChanged
A student’s progress status on a lesson changed
Practice successfully completed (reviewed)
CourseLessonPracticeCompleted
The practice moved to the “Completed” status
Course completed
CourseCompleted
The student completed all available modules and lessons of the course
Certificate issued
CertificateIssued
The student was issued a certificate
Corporate schools do not have the Payment completed and User registration for the course through payment events.
You can subscribe only to the events in the table. There are no events for signing in and out, sign-up via a referral link, submitting practice for review, practice auto-check, refunds or subscription ending.
The platform sends a POST request with a JSON body. The body has the same structure for all events:
Field
What it contains
event
The event identifier from the table above
timestamp
The moment of the event in ISO 8601 format, UTC time
idempotencyKey
A unique event key. It is the same for all your webhooks and does not change on retries
data
Event data: user, course, payment, access — the contents depend on the event
{ "data": { "...": "depends on the event" }, "event": "PaymentCompleted", "timestamp": "2026-10-15T09:30:12.512Z", "idempotencyKey": "7qqQL3bE0o8R8d3X"}
Only fields from the public event description go into data, extra fields are removed. The full contents of data for each event are described in the API documentation.
Each request has a signature header. It is an HMAC-SHA256 of the raw request body, calculated with the webhook secret key and written in hexadecimal.
1
Copy the secret key
In the webhook menu, choose Copy signature — the 64-character secret key is copied to the clipboard. The same link is in the edit form above the Link field. Save the key in your service settings.
2
Calculate the signature on your side
Calculate the HMAC-SHA256 of the request body exactly as it arrived. Do not parse the JSON and build it again: a different field order or whitespace gives a different signature.
3
Compare the signatures
If the signatures do not match, reject the request.
About 11, 33, 77 and 165 minutes. The last attempt is about 4 hours 46 minutes after the first one
Delivery order
Not guaranteed
Any other response, exceeding 15 seconds or a network error counts as a failure. After 5 failed attempts, the event is no longer sent. If the webhook was turned off while retries were in progress, the remaining attempts are canceled.
Respond with code 200 right after verifying the signature, and do heavy processing in the background. Use idempotencyKey so you do not process the same event twice: on a retry, the key is the same.
If a webhook has not accepted a single event for 14 days in a row, the platform turns it off. The school owner gets a notification and a Webhook disabled email with the webhook address.A disabled webhook does not receive new events. To receive events again, fix the address or the service and turn the webhook on with the switch in the list. After you turn it on manually, the 14-day countdown starts again.
You send a test event with the arrow button next to an event in the webhook form. The button is shown only for events that can be selected.
The test is sent to the address from the Link field, even if the webhook is not saved yet.
The body contains sample event data in the same format as real events.
The test is sent once, without retries.
A test from the form of a saved webhook is signed with its secret key. A test from the form of a new webhook is signed with a temporary key, so you cannot verify its signature.
You can send no more than 100 tests in 10 minutes.
Why does my service respond with 204, but events arrive again?
Only 200, 201 and 202 responses count as successful. The platform treats a 204 response as a failure and retries the delivery. Respond with code 200.
Why does the signature not match?
Most often, the signature is calculated from re-built JSON instead of the raw request body. Calculate the HMAC-SHA256 of the body exactly as it arrived. Also check that you use the key from Copy signature of this exact webhook and that the test was sent from the form of a saved webhook.
How many times does the platform retry the delivery?
Up to 5 attempts in total: the first one and 4 retries with pauses of about 11, 33, 77 and 165 minutes. After that, the event is considered undelivered.
Why did the webhook turn off by itself?
A webhook turns off if it has not accepted a single event for 14 days in a row. The school owner gets a notification and an email about it. Fix the address or the service and turn the webhook on with the switch.
Can I create more than 5 webhooks?
No, a school can have no more than 5 webhooks. Delete the ones you do not use or subscribe one webhook to several events. If the limit is not enough, write to support.
Do events arrive in the order they happened?
No, the order is not guaranteed: events are sent in parallel, and failed attempts are retried later. Rely on the timestamp field.
Why are there no events for signing in or refunds in the list?
Such events are not sent, so you can’t subscribe to them. 13 events from the table above are available for subscription (in a corporate school — 11).
Will the webhook arrive if I subscribe two addresses to the same event?
Yes. The event goes to each active webhook subscribed to it, with the same idempotencyKey.
Where can I see what exactly comes in the event data?
Send a test event to your address and look at the request body. The full description of each event’s data is in the API documentation.
Updated: 2026-10-17 07:07 UTC
{"docsConfig":{"theme":"mint","$schema":"https://mintlify.com/docs.json","name":"Exode — Product documentation","colors":{"primary":"#0e32cf","light":"#3a9cfc","dark":"#0e32cf"},"logo":{"light":"https://mintcdn.com/exode-faq/RM155sfnW-prDXlV/logo/dark.svg?fit=max\u0026auto=format\u0026n=RM155sfnW-prDXlV\u0026q=85\u0026s=226b8c34c13ebf24544dd5b62ed2a413","dark":"https://mintcdn.com/exode-faq/nK1GoAso5impPdQT/logo/light.svg?fit=max\u0026auto=format\u0026n=nK1GoAso5impPdQT\u0026q=85\u0026s=e9f1d77186583292eb8e9ff7839322c2"},"favicon":"https://mintcdn.com/exode-faq/Qt_iwajwYtzMNgtj/favicon.ico?fit=max\u0026auto=format\u0026n=Qt_iwajwYtzMNgtj\u0026q=85\u0026s=ab877d4deb8ab432bb3f59ae7580f411","navbar":{"links":[{"icon":"phone","href":"tel:781139570","label":"78-113-95-70"},{"href":"https://docs.exode.biz/en/welcome","label":"API"}],"primary":{"type":"button","label":"Support","href":"https://t.me/exode_biz_support"}},"footer":{"socials":{"telegram":"https://t.me/exode_biz","instagram":"https://www.instagram.com/exode_uz"}},"styling":{"latex":false},"navigation":{"languages":[{"language":"en","navbar":{"links":[{"icon":"phone","href":"tel:781139570","label":"78-113-95-70"},{"href":"https://docs.exode.biz/en/welcome","label":"API"}],"primary":{"type":"button","label":"Support","href":"https://t.me/exode_biz_support"}},"href":"en/welcome"},{"language":"ru","navbar":{"links":[{"icon":"phone","href":"tel:781139570","label":"78-113-95-70"},{"href":"https://docs.exode.biz/ru/welcome","label":"API"}],"primary":{"type":"button","label":"Поддержка","href":"https://t.me/exode_biz_support"}},"href":"ru/welcome"}]}},"deployment":{"deploymentMetadataValue":{"subdomain":"exode-faq","hasAudioNarration":false,"actualSubdomain":"exode-faq","customDomains":["faq.exode.biz"],"gitSource":{"type":"github","owner":"aslanorlov","repo":"exode-faq","deployBranch":"main","contentDirectory":"","isPrivate":true,"mountPath":"","label":""},"chroma":{"collectionId":"f8ffc1b8-5299-4bc5-b826-b92fb598100f","liveDeploymentHistoryId":"6ad325650d7b01a838871775","schemaVersion":"v3","latestBuiltDeploymentHistoryId":"6ad325650d7b01a838871775"},"entitlements":{},"buildId":"6ad325650d7b01a838871775","clientVersion":"0.0.4021","searchFilterCounts":[{"language":"en","tag":"General","count":54},{"language":"ru","tag":"Общее","count":54},{"language":"en","tag":"Corporate training and HR","count":14},{"language":"ru","tag":"Корпобучение и HR","count":14},{"language":"en","tag":"Online schools","count":12},{"language":"ru","tag":"Онлайн-школы","count":12},{"language":"en","tag":"Getting started","count":7},{"language":"en","tag":"Courses","count":7},{"language":"en","tag":"School setup","count":7},{"language":"ru","tag":"Начало работы","count":7},{"language":"ru","tag":"Курсы","count":7},{"language":"ru","tag":"Настройка школы","count":7},{"language":"en","tag":"Sales and payments","count":6},{"language":"ru","tag":"Продажи и платежи","count":6},{"language":"en","tag":"Communication and content","count":5},{"language":"en","tag":"Lessons and modules","count":5},{"language":"en","tag":"School customization","count":5},{"language":"ru","tag":"Общение и контент","count":5},{"language":"ru","tag":"Уроки и модули","count":5},{"language":"ru","tag":"Кастомизация школы","count":5}]},"assistantConfig":{"enableAskAiButton":false,"enableStarterQuestions":false,"enableContextualStarterQuestions":false,"enableAskAiSearchBar":true},"basePath":"","hasChatPermissions":false,"hasWebMcpPermissions":false,"toggles":[{"name":"kill-isr","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-mintlify-slack","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-integrations-settings","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-pr-grouping","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-prefix","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"analytics-v7","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"user-flows-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"no-publish-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-onboarding","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"llms-txt-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving-live","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"stripTemperatureMiddleware","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"per-site-search-delta","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"support-platform-pylon-zendesk","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"instant-rollback","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications-server","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-private-page-search","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-fable","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fixed-ai-action-pricing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automation-resolve-merge-conflict","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"scoped-publishing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"mintql","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"page-level-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-version-history-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-page-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workspace-projects","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"assistant-v5","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"byok-sep-25","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fork-pr-previews","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"revalidation-jwt","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"vite-origin","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-updated-9-15","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-sso-scim","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"hobby-abuse-scan","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"dashboard-inbox","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-signup","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-enabled","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"googleFontsInlined":false},"customCss":[],"page":{"pagePath":"en/general/developers/webhooks","slug":"/en/general/developers/webhooks","pageMetadata":{"title":"Webhooks","description":"How to set up school webhooks: available events, request format, signature verification, retries and automatic endpoint disabling.","href":"/en/general/developers/webhooks"},"description":{"compiledSource":"\"use strict\";\nconst {jsx: _jsx} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n p: \"p\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsx(_components.p, {\n children: \"How to set up school webhooks: available events, request format, signature verification, retries and automatic endpoint disabling.\"\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = {\n ..._provideComponents(),\n ...props.components\n };\n return MDXLayout ? _jsx(MDXLayout, {\n ...props,\n children: _jsx(_createMdxContent, {\n ...props\n })\n }) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx,jsxs:_jsxs}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){const _components={a:`a`,code:`code`,em:`em`,hr:`hr`,li:`li`,p:`p`,pre:`pre`,span:`span`,strong:`strong`,tbody:`tbody`,td:`td`,th:`th`,thead:`thead`,tr:`tr`,ul:`ul`,..._provideComponents(),...props.components},{Accordion,AccordionGroup,CodeBlock,Heading,Note,Step,Steps,Table,Tip,Warning}=_components;if(!Accordion)_missingMdxReference(`Accordion`,true);if(!AccordionGroup)_missingMdxReference(`AccordionGroup`,true);if(!CodeBlock)_missingMdxReference(`CodeBlock`,true);if(!Heading)_missingMdxReference(`Heading`,true);if(!Note)_missingMdxReference(`Note`,true);if(!Step)_missingMdxReference(`Step`,true);if(!Steps)_missingMdxReference(`Steps`,true);if(!Table)_missingMdxReference(`Table`,true);if(!Tip)_missingMdxReference(`Tip`,true);if(!Warning)_missingMdxReference(`Warning`,true);return _jsxs(_Fragment,{children:[_jsx(_components.p,{children:`Webhooks automatically notify your external services about events in the school: sign-up, payment (commercial schools only), course enrollment, course completion. When an event happens, the platform sends a POST request with the event data to the address you specified. This is how you connect the school to CRMs, bots, spreadsheets and your own services without constantly polling the API.`}),`\n`,_jsx(Heading,{level:`2`,id:`where-to-set-them-up`,children:`Where to set them up`}),`\n`,_jsxs(_components.p,{children:[`Webhooks are in `,_jsx(_components.strong,{children:`Control panel → My school → For developers → Webhooks`}),` (in a corporate school — `,_jsx(_components.strong,{children:`Control panel → Company → For developers → Webhooks`}),`). A staff member with the `,_jsx(_components.strong,{children:`School settings management`}),` permission (in a corporate school — `,_jsx(_components.strong,{children:`Company settings management`}),`) can work with them.`]}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:`A school can have no more than 5 webhooks. Deleted webhooks do not count toward the limit.`}),`\n`,_jsx(_components.li,{children:`Each webhook is one address and a set of events it is subscribed to.`}),`\n`,_jsx(_components.li,{children:`Webhooks are set up only in the control panel. You cannot manage them through the school API.`}),`\n`]}),`\n`,_jsx(Heading,{level:`2`,id:`how-to-create-a-webhook`,children:`How to create a webhook`}),`\n`,_jsxs(Steps,{children:[_jsx(Step,{title:`Click “Create a webhook”`,children:_jsxs(_components.p,{children:[`Open the `,_jsx(_components.strong,{children:`Webhooks`}),` section (in a corporate school — `,_jsx(_components.strong,{children:`Company webhooks`}),`) and click `,_jsx(_components.strong,{children:`Create a webhook`}),`.`]})}),_jsx(Step,{title:`Enter the address`,children:_jsxs(_components.p,{children:[`In the `,_jsx(_components.strong,{children:`Link`}),` field, enter the address of your service, for example `,_jsx(_components.a,{href:`https://my-service.com/api/exode`,children:`https://my-service.com/api/exode`}),`. The address can be up to 255 characters long. Use HTTPS.`]})}),_jsx(Step,{title:`Add a note`,children:_jsxs(_components.p,{children:[`In the `,_jsx(_components.strong,{children:`Service note`}),` field, write what this webhook is for, for example “AmoCRM integration”. The note is visible only in the webhook list and can be up to 255 characters long.`]})}),_jsx(Step,{title:`Choose the events`,children:_jsxs(_components.p,{children:[`Select the events in the `,_jsx(_components.strong,{children:`Selected events`}),` list. The counter on the right shows how many events are selected.`]})}),_jsx(Step,{title:`Check delivery`,children:_jsx(_components.p,{children:`Click the arrow next to an event to send a test event to the specified address. Make sure your service received the request.`})}),_jsx(Step,{title:`Save the webhook`,children:_jsxs(_components.p,{children:[`Click `,_jsx(_components.strong,{children:`Create`}),`. On creation, the platform generates a secret key for signature verification.`]})})]}),`\n`,_jsxs(_components.p,{children:[`In the webhook list, each row has an `,_jsx(_components.strong,{children:`Active`}),` / `,_jsx(_components.strong,{children:`Inactive`}),` switch and an action menu: `,_jsx(_components.strong,{children:`Edit`}),`, `,_jsx(_components.strong,{children:`Copy signature`}),` and `,_jsx(_components.strong,{children:`Delete webhook`}),`.`]}),`\n`,_jsx(Heading,{level:`2`,id:`events`,children:`Events`}),`\n`,_jsx(_components.p,{children:`13 events are available for subscription (in a corporate school — 11):`}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`Event in the control panel`}),_jsx(_components.th,{children:`Identifier`}),_jsx(_components.th,{children:`When it fires`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`The user has registered`}),_jsx(_components.td,{children:_jsx(_components.code,{children:`UserSignedUp`})}),_jsx(_components.td,{children:`A new user signed up for the school on their own`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`The user has filled in the data`}),_jsx(_components.td,{children:_jsx(_components.code,{children:`UserAcquainted`})}),_jsx(_components.td,{children:`The user filled in and submitted the introduction form`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`User connected Telegram`}),_jsx(_components.td,{children:_jsx(_components.code,{children:`UserTgConnected`})}),_jsx(_components.td,{children:`The user linked or changed Telegram`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`User created via LMS`}),_jsx(_components.td,{children:_jsx(_components.code,{children:`UserCreatedViaLms`})}),_jsx(_components.td,{children:`A school staff member created the user manually`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Enrollment in a free course`}),_jsx(_components.td,{children:_jsx(_components.code,{children:`ProductEnrolledToFree`})}),_jsx(_components.td,{children:`The user enrolled in a free course`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Registration for the course via LMS`}),_jsx(_components.td,{children:_jsx(_components.code,{children:`ProductEnrolledViaLms`})}),_jsx(_components.td,{children:`A school staff member granted access manually`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`User registration for the course through payment`}),_jsx(_components.td,{children:_jsx(_components.code,{children:`ProductEnrolledViaPayment`})}),_jsx(_components.td,{children:`Access was granted after a successful payment (commercial schools only)`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Enrollment via invite link`}),_jsx(_components.td,{children:_jsx(_components.code,{children:`ProductEnrolledByInviteLink`})}),_jsx(_components.td,{children:`The user enrolled via an invite link`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Payment completed`}),_jsx(_components.td,{children:_jsx(_components.code,{children:`PaymentCompleted`})}),_jsx(_components.td,{children:`The payment was successfully paid (commercial schools only)`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Course progress updated`}),_jsx(_components.td,{children:_jsx(_components.code,{children:`CourseProgressChanged`})}),_jsx(_components.td,{children:`A student’s progress status on a lesson changed`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Practice successfully completed (reviewed)`}),_jsx(_components.td,{children:_jsx(_components.code,{children:`CourseLessonPracticeCompleted`})}),_jsx(_components.td,{children:`The practice moved to the “Completed” status`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Course completed`}),_jsx(_components.td,{children:_jsx(_components.code,{children:`CourseCompleted`})}),_jsx(_components.td,{children:`The student completed all available modules and lessons of the course`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Certificate issued`}),_jsx(_components.td,{children:_jsx(_components.code,{children:`CertificateIssued`})}),_jsx(_components.td,{children:`The student was issued a certificate`})]})]})]}),`\n`,_jsxs(_components.p,{children:[`Corporate schools do not have the `,_jsx(_components.strong,{children:`Payment completed`}),` and `,_jsx(_components.strong,{children:`User registration for the course through payment`}),` events.`]}),`\n`,_jsx(Note,{children:_jsx(_components.p,{children:`You can subscribe only to the events in the table. There are no events for signing in and out, sign-up via a referral link, submitting practice for review, practice auto-check, refunds or subscription ending.`})}),`\n`,_jsx(Heading,{level:`2`,id:`request-format`,children:`Request format`}),`\n`,_jsx(_components.p,{children:`The platform sends a POST request with a JSON body. The body has the same structure for all events:`}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`Field`}),_jsx(_components.th,{children:`What it contains`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`event`})}),_jsx(_components.td,{children:`The event identifier from the table above`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`timestamp`})}),_jsx(_components.td,{children:`The moment of the event in ISO 8601 format, UTC time`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`idempotencyKey`})}),_jsx(_components.td,{children:`A unique event key. It is the same for all your webhooks and does not change on retries`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`data`})}),_jsx(_components.td,{children:`Event data: user, course, payment, access — the contents depend on the event`})]})]})]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`6`,language:`json`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`json`,children:_jsxs(_components.code,{language:`json`,numberOfLines:`6`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`{`})}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"data\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: { `}),_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:`\"...\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"depends on the event\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` },`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"event\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"PaymentCompleted\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"timestamp\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"2026-10-15T09:30:12.512Z\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#9CDCFE`},children:` \"idempotencyKey\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"7qqQL3bE0o8R8d3X\"`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`}`})}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[`Only fields from the public event description go into `,_jsx(_components.code,{children:`data`}),`, extra fields are removed. The full contents of `,_jsx(_components.code,{children:`data`}),` for each event are described in the `,_jsx(_components.a,{href:`https://docs.exode.biz/en/exode-api/webhooks/about`,children:`API documentation`}),`.`]}),`\n`,_jsx(Heading,{level:`2`,id:`signature-verification`,children:`Signature verification`}),`\n`,_jsxs(_components.p,{children:[`Each request has a `,_jsx(_components.code,{children:`signature`}),` header. It is an HMAC-SHA256 of the raw request body, calculated with the webhook secret key and written in hexadecimal.`]}),`\n`,_jsxs(Steps,{children:[_jsx(Step,{title:`Copy the secret key`,children:_jsxs(_components.p,{children:[`In the webhook menu, choose `,_jsx(_components.strong,{children:`Copy signature`}),` — the 64-character secret key is copied to the clipboard. The same link is in the edit form above the `,_jsx(_components.strong,{children:`Link`}),` field. Save the key in your service settings.`]})}),_jsx(Step,{title:`Calculate the signature on your side`,children:_jsx(_components.p,{children:`Calculate the HMAC-SHA256 of the request body exactly as it arrived. Do not parse the JSON and build it again: a different field order or whitespace gives a different signature.`})}),_jsx(Step,{title:`Compare the signatures`,children:_jsx(_components.p,{children:`If the signatures do not match, reject the request.`})})]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`27`,language:`javascript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`javascript`,children:_jsxs(_components.code,{language:`javascript`,numberOfLines:`27`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:`import`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` crypto`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:` from`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` 'crypto'`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`;`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:`import`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` express`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:` from`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` 'express'`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`;`})]}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:`const`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#4FC1FF`},children:` app`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:` =`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:` express`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`();`})]}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`app`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`post`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`'/webhooks/exode'`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`express`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`raw`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`({ `}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`type:`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` 'application/json'`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` }), (`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`req`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`res`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`) `}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:`=>`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` {`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:` const`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#4FC1FF`},children:` rawBody`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:` =`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` req`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`body`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`toString`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`'utf8'`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`);`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:` const`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#4FC1FF`},children:` received`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:` =`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` req`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`header`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`'signature'`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`) `}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`||`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` ''`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`;`})]}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:` const`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#4FC1FF`},children:` expected`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:` =`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` crypto`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` .`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`createHmac`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`'sha256'`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`process`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`env`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#4FC1FF`},children:`EXODE_WEBHOOK_SECRET`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`)`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` .`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`update`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`rawBody`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`)`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` .`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`digest`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`'hex'`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`);`})]}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:` const`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#4FC1FF`},children:` isValid`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:` =`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` received`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#9CDCFE`},children:`length`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:` ===`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` expected`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#9CDCFE`},children:`length`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:` \u0026\u0026`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` crypto`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`timingSafeEqual`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`Buffer`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`from`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`received`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`), `}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`Buffer`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`from`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`expected`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`));`})]}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:` if`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` (`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`!`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`isValid`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`) {`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:` return`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` res`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`status`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`401`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`).`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`send`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`'invalid signature'`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`);`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` }`})}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#569CD6`},children:` const`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#4FC1FF`},children:` payload`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:` =`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#4FC1FF`},children:` JSON`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`parse`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:`rawBody`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`);`})]}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:` /* Handle the event and remember payload.idempotencyKey */`})}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` res`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`.`}),_jsx(_components.span,{style:{color:`#8250DF`,\"--shiki-dark\":`#DCDCAA`},children:`sendStatus`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`200`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`);`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`});`})}),`\n`]})})}),`\n`,_jsx(Warning,{children:_jsx(_components.p,{children:`The secret key is confidential data. Keep it only on the server and do not publish it in the school’s custom code or in public repositories.`})}),`\n`,_jsx(Heading,{level:`2`,id:`delivery-and-retries`,children:`Delivery and retries`}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`Parameter`}),_jsx(_components.th,{children:`Value`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Method`}),_jsx(_components.td,{children:`POST, JSON body`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Response timeout`}),_jsx(_components.td,{children:`15 seconds`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Successful response`}),_jsx(_components.td,{children:`HTTP codes 200, 201 and 202`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Number of attempts`}),_jsx(_components.td,{children:`Up to 5: the first delivery and up to 4 retries`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Pauses between attempts`}),_jsx(_components.td,{children:`About 11, 33, 77 and 165 minutes. The last attempt is about 4 hours 46 minutes after the first one`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Delivery order`}),_jsx(_components.td,{children:`Not guaranteed`})]})]})]}),`\n`,_jsx(_components.p,{children:`Any other response, exceeding 15 seconds or a network error counts as a failure. After 5 failed attempts, the event is no longer sent. If the webhook was turned off while retries were in progress, the remaining attempts are canceled.`}),`\n`,_jsx(Tip,{children:_jsxs(_components.p,{children:[`Respond with code 200 right after verifying the signature, and do heavy processing in the background. Use `,_jsx(_components.code,{children:`idempotencyKey`}),` so you do not process the same event twice: on a retry, the key is the same.`]})}),`\n`,_jsx(Heading,{level:`2`,id:`automatic-disabling`,children:`Automatic disabling`}),`\n`,_jsxs(_components.p,{children:[`If a webhook has not accepted a single event for 14 days in a row, the platform turns it off. The school owner gets a notification and a `,_jsx(_components.strong,{children:`Webhook disabled`}),` email with the webhook address.`]}),`\n`,_jsx(_components.p,{children:`A disabled webhook does not receive new events. To receive events again, fix the address or the service and turn the webhook on with the switch in the list. After you turn it on manually, the 14-day countdown starts again.`}),`\n`,_jsx(Heading,{level:`2`,id:`test-events`,children:`Test events`}),`\n`,_jsx(_components.p,{children:`You send a test event with the arrow button next to an event in the webhook form. The button is shown only for events that can be selected.`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[`The test is sent to the address from the `,_jsx(_components.strong,{children:`Link`}),` field, even if the webhook is not saved yet.`]}),`\n`,_jsx(_components.li,{children:`The body contains sample event data in the same format as real events.`}),`\n`,_jsx(_components.li,{children:`The test is sent once, without retries.`}),`\n`,_jsx(_components.li,{children:`A test from the form of a saved webhook is signed with its secret key. A test from the form of a new webhook is signed with a temporary key, so you cannot verify its signature.`}),`\n`,_jsx(_components.li,{children:`You can send no more than 100 tests in 10 minutes.`}),`\n`]}),`\n`,_jsx(Heading,{level:`2`,id:`frequently-asked-questions`,children:`Frequently asked questions`}),`\n`,_jsxs(AccordionGroup,{children:[_jsx(Accordion,{title:`Why does my service respond with 204, but events arrive again?`,id:`why-does-my-service-respond-with-204-but-events-arrive-again`,children:_jsx(_components.p,{children:`Only 200, 201 and 202 responses count as successful. The platform treats a 204 response as a failure and retries the delivery. Respond with code 200.`})}),_jsx(Accordion,{title:`Why does the signature not match?`,id:`why-does-the-signature-not-match`,children:_jsxs(_components.p,{children:[`Most often, the signature is calculated from re-built JSON instead of the raw request body. Calculate the HMAC-SHA256 of the body exactly as it arrived. Also check that you use the key from `,_jsx(_components.strong,{children:`Copy signature`}),` of this exact webhook and that the test was sent from the form of a saved webhook.`]})}),_jsx(Accordion,{title:`How many times does the platform retry the delivery?`,id:`how-many-times-does-the-platform-retry-the-delivery`,children:_jsx(_components.p,{children:`Up to 5 attempts in total: the first one and 4 retries with pauses of about 11, 33, 77 and 165 minutes. After that, the event is considered undelivered.`})}),_jsx(Accordion,{title:`Why did the webhook turn off by itself?`,id:`why-did-the-webhook-turn-off-by-itself`,children:_jsx(_components.p,{children:`A webhook turns off if it has not accepted a single event for 14 days in a row. The school owner gets a notification and an email about it. Fix the address or the service and turn the webhook on with the switch.`})}),_jsx(Accordion,{title:`Can I create more than 5 webhooks?`,id:`can-i-create-more-than-5-webhooks`,children:_jsx(_components.p,{children:`No, a school can have no more than 5 webhooks. Delete the ones you do not use or subscribe one webhook to several events. If the limit is not enough, write to support.`})}),_jsx(Accordion,{title:`Do events arrive in the order they happened?`,id:`do-events-arrive-in-the-order-they-happened`,children:_jsxs(_components.p,{children:[`No, the order is not guaranteed: events are sent in parallel, and failed attempts are retried later. Rely on the `,_jsx(_components.code,{children:`timestamp`}),` field.`]})}),_jsx(Accordion,{title:`Why are there no events for signing in or refunds in the list?`,id:`why-are-there-no-events-for-signing-in-or-refunds-in-the-list`,children:_jsx(_components.p,{children:`Such events are not sent, so you can’t subscribe to them. 13 events from the table above are available for subscription (in a corporate school — 11).`})}),_jsx(Accordion,{title:`Will the webhook arrive if I subscribe two addresses to the same event?`,id:`will-the-webhook-arrive-if-i-subscribe-two-addresses-to-the-same-event`,children:_jsxs(_components.p,{children:[`Yes. The event goes to each active webhook subscribed to it, with the same `,_jsx(_components.code,{children:`idempotencyKey`}),`.`]})}),_jsx(Accordion,{title:`Where can I see what exactly comes in the event data?`,id:`where-can-i-see-what-exactly-comes-in-the-event-data`,children:_jsxs(_components.p,{children:[`Send a test event to your address and look at the request body. The full description of each event’s data is in the `,_jsx(_components.a,{href:`https://docs.exode.biz/en/exode-api/webhooks/about`,children:`API documentation`}),`.`]})})]}),`\n`,_jsx(_components.hr,{}),`\n`,_jsx(_components.p,{children:_jsx(_components.em,{children:`Updated: 2026-10-17 07:07 UTC`})})]})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent};function _missingMdxReference(id,component){throw new Error(`Expected `+(component?`component`:`object`)+\" `\"+id+\"` to be defined: you likely forgot to import, pass, or provide it.\")}","frontmatter":{},"scope":{"config":{},"pageMetadata":{"title":"Webhooks","description":"How to set up school webhooks: available events, request format, signature verification, retries and automatic endpoint disabling.","href":"/en/general/developers/webhooks"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[{"title":"Where to set them up","slug":"where-to-set-them-up","depth":2,"children":[]},{"title":"How to create a webhook","slug":"how-to-create-a-webhook","depth":2,"children":[]},{"title":"Events","slug":"events","depth":2,"children":[]},{"title":"Request format","slug":"request-format","depth":2,"children":[]},{"title":"Signature verification","slug":"signature-verification","depth":2,"children":[]},{"title":"Delivery and retries","slug":"delivery-and-retries","depth":2,"children":[]},{"title":"Automatic disabling","slug":"automatic-disabling","depth":2,"children":[]},{"title":"Test events","slug":"test-events","depth":2,"children":[]},{"title":"Frequently asked questions","slug":"frequently-asked-questions","depth":2,"children":[]}],"codeExamples":{}},"jsLinks":["/_mintlify/custom/exode-faq/0f03cceb9db709c8/lang-detect.js"],"subdomain":"exode-faq","siteName":"Exode — Product documentation","title":"Webhooks - Exode — Product documentation","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{"en":{"href":"/en/general/developers/webhooks","matchLevel":0},"ru":{"href":"/ru/general/developers/webhooks","matchLevel":1}}},"lastModified":"2026-10-17T07:36:14.439Z","originalFileLocation":"en/general/developers/webhooks.mdx","locale":"en"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=null;if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"system\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"description\" content=\"How to set up school webhooks: available events, request format, signature verification, retries and automatic endpoint disabling.\">\u003cmeta data-mint-head name=\"application-name\" content=\"Exode — Product documentation\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/exode-faq/Wp7VM_dlt_4QlPSi/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/mintlify-assets/_mintlify/favicons/exode-faq/Wp7VM_dlt_4QlPSi/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/exode-faq/Wp7VM_dlt_4QlPSi/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/exode-faq/Wp7VM_dlt_4QlPSi/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/exode-faq/Wp7VM_dlt_4QlPSi/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/exode-faq/Wp7VM_dlt_4QlPSi/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/exode-faq/Wp7VM_dlt_4QlPSi/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/exode-faq/Wp7VM_dlt_4QlPSi/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/exode-faq/Wp7VM_dlt_4QlPSi/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"Webhooks - Exode — Product documentation\">\u003cmeta data-mint-head property=\"og:description\" content=\"How to set up school webhooks: available events, request format, signature verification, retries and automatic endpoint disabling.\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"Exode — Product documentation\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://faq.exode.biz/en/general/developers/webhooks\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://exode-faq.mintlify.app/_mintlify/api/og/v2?division=For+developers\u0026amp;title=Webhooks\u0026amp;description=How+to+set+up+school+webhooks%3A+available+events%2C+request+format%2C+signature+verification%2C+retries+and+automatic+endpoint+disabling.\u0026amp;theme=4b81d533b2a2f5ec2be885ba\u0026amp;signature=8fbba41abfe26c8da10219e5bffa3e1c603cebb96aee797c1997609a8ace3f1e\">\u003cmeta data-mint-head property=\"og:image:width\" content=\"1200\">\u003cmeta data-mint-head property=\"og:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"Webhooks - Exode — Product documentation\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"How to set up school webhooks: available events, request format, signature verification, retries and automatic endpoint disabling.\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://exode-faq.mintlify.app/_mintlify/api/og/v2?division=For+developers\u0026amp;title=Webhooks\u0026amp;description=How+to+set+up+school+webhooks%3A+available+events%2C+request+format%2C+signature+verification%2C+retries+and+automatic+endpoint+disabling.\u0026amp;theme=4b81d533b2a2f5ec2be885ba\u0026amp;signature=8fbba41abfe26c8da10219e5bffa3e1c603cebb96aee797c1997609a8ace3f1e\">\u003cmeta data-mint-head name=\"twitter:image:width\" content=\"1200\">\u003cmeta data-mint-head name=\"twitter:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/mintlify-assets/_mintlify/favicons/exode-faq/Wp7VM_dlt_4QlPSi/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"Exode — Product documentation\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#0e32cf\">\u003clink data-mint-head rel=\"canonical\" href=\"https://faq.exode.biz/en/general/developers/webhooks\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/en/general/developers/webhooks.md\">\u003clink data-mint-head rel=\"alternate\" hreflang=\"en\" href=\"https://faq.exode.biz/en/general/developers/webhooks\">\u003clink data-mint-head rel=\"alternate\" hreflang=\"ru\" href=\"https://faq.exode.biz/ru/general/developers/webhooks\">\u003clink data-mint-head rel=\"alternate\" hreflang=\"x-default\" href=\"https://faq.exode.biz/en/general/developers/webhooks\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://faq.exode.biz/#organization\",\"name\":\"Exode — Product documentation\",\"url\":\"https://faq.exode.biz\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://mintcdn.com/exode-faq/RM155sfnW-prDXlV/logo/dark.svg?fit=max\u0026auto=format\u0026n=RM155sfnW-prDXlV\u0026q=85\u0026s=226b8c34c13ebf24544dd5b62ed2a413\"}},{\"@type\":\"WebSite\",\"@id\":\"https://faq.exode.biz#website\",\"name\":\"Exode — Product documentation\",\"url\":\"https://faq.exode.biz\",\"publisher\":{\"@id\":\"https://faq.exode.biz/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://faq.exode.biz/en/general/developers/webhooks#webpage\",\"url\":\"https://faq.exode.biz/en/general/developers/webhooks\",\"name\":\"Webhooks\",\"description\":\"How to set up school webhooks: available events, request format, signature verification, retries and automatic endpoint disabling.\",\"dateModified\":\"2026-10-17T07:36:14.439Z\",\"isPartOf\":{\"@id\":\"https://faq.exode.biz#website\"},\"breadcrumb\":{\"@id\":\"https://faq.exode.biz/en/general/developers/webhooks#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://faq.exode.biz/en/general/developers/webhooks#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"For developers\",\"item\":\"https://faq.exode.biz/en/general/developers/technical-overview\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Webhooks\",\"item\":\"https://faq.exode.biz/en/general/developers/webhooks\"}]},{\"@type\":[\"Article\",\"TechArticle\"],\"@id\":\"https://faq.exode.biz/en/general/developers/webhooks#article\",\"headline\":\"Webhooks\",\"name\":\"Webhooks\",\"description\":\"How to set up school webhooks: available events, request format, signature verification, retries and automatic endpoint disabling.\",\"url\":\"https://faq.exode.biz/en/general/developers/webhooks\",\"mainEntityOfPage\":{\"@id\":\"https://faq.exode.biz/en/general/developers/webhooks#webpage\"},\"dateModified\":\"2026-10-17T07:36:14.439Z\",\"publisher\":{\"@id\":\"https://faq.exode.biz/#organization\"},\"isPartOf\":{\"@id\":\"https://faq.exode.biz#website\"}}]}\u003c/script>\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"Exode — Product documentation\",\"creator\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","siteName":"Exode — Product documentation","nav":{"global":null,"languages":[{"language":"en","navbar":{"links":[{"icon":"phone","href":"tel:781139570","label":"78-113-95-70"},{"href":"https://docs.exode.biz/en/welcome","label":"API"}],"primary":{"type":"button","label":"Support","href":"https://t.me/exode_biz_support"}},"tabs":[{"tab":"General","groups":[{"group":"Getting started","pages":[{"title":"Introduction","href":"/en/welcome"},{"title":"Platform overview","href":"/en/general/getting-started/overview"}]},{"group":"Creating a school","pages":[{"title":"Registration and school creation","href":"/en/general/school-creation/registration"},{"title":"School type: course sales or corporate training","href":"/en/general/school-creation/school-type"},{"title":"School address and domain","href":"/en/general/school-creation/domain"}]},{"group":"School setup","pages":[{"group":"School customization","root":{"title":"School customization","href":"/en/general/school-setup/customization"},"pages":[{"title":"Logo and icon","href":"/en/general/school-setup/logo-and-icon"},{"title":"Themes","href":"/en/general/school-setup/themes"},{"title":"Course catalog","href":"/en/general/school-setup/course-catalog"},{"title":"Left site menu","href":"/en/general/school-setup/left-menu"},{"title":"Website footer","href":"/en/general/school-setup/footer"}]},{"title":"Available languages","href":"/en/general/school-setup/languages"},{"title":"Login and registration","href":"/en/general/school-setup/registration-settings"}]},{"group":"Roles and permissions","pages":[{"title":"Roles in the school","href":"/en/general/roles-permissions/user-roles"},{"title":"Access rights","href":"/en/general/roles-permissions/access-rights"}]},{"group":"Courses","pages":[{"title":"Course settings","href":"/en/general/courses/settings"},{"title":"Course types","href":"/en/general/courses/course-types"},{"title":"Creating courses","href":"/en/general/courses/creating-courses"},{"title":"Course bundles","href":"/en/general/courses/course-packages"},{"title":"Content protection","href":"/en/general/courses/content-protection"},{"title":"SEO optimization","href":"/en/general/courses/seo"},{"title":"Reviews","href":"/en/general/courses/reviews"}]},{"group":"Lessons and modules","pages":[{"title":"Creating lessons and modules","href":"/en/general/lessons-modules/creating"},{"title":"Lesson content","href":"/en/general/lessons-modules/content"},{"group":"Practice","root":{"title":"Practice","href":"/en/general/lessons-modules/practice"},"pages":[{"title":"Lesson and practice settings","href":"/en/general/lessons-modules/settings"},{"title":"Task types","href":"/en/general/lessons-modules/task-types"},{"title":"Practice check","href":"/en/general/lessons-modules/checking-practice"}]}]},{"group":"Task database","pages":[{"title":"Creating tasks","href":"/en/general/task-bank/creating-tasks"},{"title":"Using tasks","href":"/en/general/task-bank/using-tasks"}]},{"group":"Rewards and gamification","pages":[{"group":"Certificates","root":{"title":"Certificates","href":"/en/general/gamification/certificates"},"pages":[{"title":"Setting up certificates","href":"/en/general/gamification/creating-certificates"},{"title":"Editing the certificate","href":"/en/general/gamification/editing-certificates"},{"title":"Where the student sees the certificate","href":"/en/general/gamification/certificate-display"}]},{"title":"Gamification: stars and rating","href":"/en/general/gamification/gamification"}]},{"group":"People","pages":[{"title":"Adding users","href":"/en/general/users/adding-users"},{"title":"Course access","href":"/en/general/users/adding-to-course"},{"title":"User card","href":"/en/general/users/user-card"},{"title":"Video watch filter","href":"/en/general/users/video-watch-filter"}]},{"group":"Groups","pages":[{"title":"Groups and cohorts","href":"/en/general/groups/creating-groups"},{"title":"Group settings","href":"/en/general/groups/group-settings"}]},{"group":"Communication and content","pages":[{"title":"Messenger","href":"/en/general/communication/messenger"},{"title":"Notifications","href":"/en/general/communication/notifications"},{"title":"Knowledge base and AI assistant","href":"/en/general/communication/knowledge-base"},{"title":"Stories","href":"/en/general/communication/stories"},{"title":"School Telegram bot","href":"/en/general/communication/telegram-bot"}]},{"group":"Analytics","pages":[{"title":"Home analytics","href":"/en/general/analytics/main-analytics"},{"title":"How metrics are calculated","href":"/en/general/analytics/detailed-analytics"},{"title":"Tables and exports","href":"/en/general/analytics/dashboards"},{"title":"Student progress","href":"/en/general/analytics/personal-analytics"}]},{"group":"Mobile app","pages":[{"title":"Mobile app","href":"/en/general/mobile-app/mobile-app"}]},{"group":"Reference","pages":[{"title":"Glossary","href":"/en/general/reference/glossary"},{"title":"What's available on the platform","href":"/en/general/reference/feature-status"}]},{"group":"For developers","pages":[{"title":"Technical documentation","description":"An overview of tools for developers and integrators: the school API, webhooks, custom code, exports, custom pages with mini apps and the Exode SDK.","href":"/en/general/developers/technical-overview"},{"title":"Webhooks","href":"/en/general/developers/webhooks"},{"title":"Custom code","description":"Your own scripts and styles on the school website: where the code goes, limits, interface settings and goal events for Yandex Metrica, Google Analytics, Meta Pixel and VK Ads.","href":"/en/general/developers/custom-code"},{"title":"API","href":"/en/general/developers/api"}]}]},{"tab":"Online schools","pages":[{"title":"Online schools","href":"/en/online-schools/getting-started/user-scenarios"}]},{"tab":"Corporate training and HR","pages":[{"title":"Corporate training and HR","href":"/en/corporate/overview"}]}]},{"language":"ru","navbar":{"links":[{"icon":"phone","href":"tel:781139570","label":"78-113-95-70"},{"href":"https://docs.exode.biz/ru/welcome","label":"API"}],"primary":{"type":"button","label":"Поддержка","href":"https://t.me/exode_biz_support"}},"pages":[{"title":"ru","href":"/ru/general/developers/webhooks"}]}]},"title":"Webhooks - Exode — Product documentation","buildId":"dpl_EnxDqhjaNc9a8MVa833TGULPd9uJ","deploymentId":"dpl_EnxDqhjaNc9a8MVa833TGULPd9uJ","dynamics":[],"chunks":[]}