๐ช Webhooks
Learn about Edusign webhooks, authentication methods, timezone handling, and how to secure your webhook endpoints with Client Secret and HMAC verification.
What is a webhook ?
A webhook is a method used in web development that allows one application to provide real-time information to another application over HTTP, it's essentially a way for apps to communicate with each other automatically.
When an event occurs in the source app, a notification is sent out as an HTTP POST request to a specified URL - this is the webhook URL. This request carries a payload with information about the event that has occurred. The receiving application, which has been configured to listen for such notifications at the given URL, can then take immediate action based on the transmitted data.
It's a simple, efficient, and server-friendly way for applications to extend their functionality and integrate with external services without the need for constant polling.
Authentication & Security Keys
Before diving into webhook security, it's important to understand the different types of authentication credentials in Edusign:
API Keys vs Client Secret
API Keys are used for general API authentication when making requests to Edusign's API endpoints. You can have up to 5 API keys per school, and they're used with the Authorization: Bearer <API_KEY> header.
Client Secret is specifically tied to Edusign Apps and is used for webhook signature verification. Here's how they differ:
| Feature | API Keys | Client Secret |
|---|---|---|
| Purpose | General API authentication | Webhook HMAC signature verification |
| Quantity | Up to 5 per school | 1 per app |
| Usage | Authorization: Bearer <API_KEY> | HMAC signature validation |
| Scope | All API endpoints | Webhook security only |
Creating an Edusign App
To obtain a Client ID and Client Secret, you need to create an Edusign App:
- Go to your school dashboard: Private Apps
- Create a new app (can remain private or be published publicly after validation)
- Your app will provide you with:
- Client ID: Public identifier for your app
- Client Secret: Private key used for webhook HMAC verification
- API Key: For making API requests (same as regular API keys)
Key Difference: While you can have multiple API keys for general API access, the Client Secret is app-specific and is specifically used for verifying webhook signatures.
Need help creating an app? Check out our detailed guide: Building an App for step-by-step instructions.
Event list
You can find the list of all the available webhook events here.
We will update and add more events in the upcoming months.
Auto retry
Currently there is no auto-retry scenario.
However, if the webhooks fails, Edusign will send a notification into the dashboard. Moreover, webhooks automatically timeout at 15 seconds.
Date and Time Format in Webhooks
Important: Date and time values in webhook payloads are expressed in your school's configured timezone, not in UTC.
When Edusign sends webhook payloads, all date and time fields (such as REQUEST_DATE, course start/end times, etc.) are automatically converted to match your school's timezone setting.
Example: If your school is configured with Paris timezone in your Edusign settings, all dates and times in webhook payloads will be in Paris time (CET/CEST), not UTC.
This means you don't need to perform timezone conversions when processing webhook data - the times are already in your school's local timezone for convenience.
Webhook security
HMAC Signature Verification
Important: HMAC signature verification only works with apps from the Edusign app marketplace. If you haven't created an Edusign App yet, please refer to the Authentication & Security Keys section above.
Webhook HMAC is a security technique where both the sender (Edusign) and receiver (your application) share a secret key - the Client Secret from your Edusign App.
When Edusign sends a webhook, it creates a unique HMAC signature using:
- Your app's Client Secret
- The webhook message content
The signature is sent in the x-edusign-hmac header. Your application should:
- Retrieve the HMAC signature from the
x-edusign-hmacheader - Use your app's Client Secret to generate your own signature
- Compare both signatures to verify authenticity
Which Client Secret is Used?
When you have multiple API keys but only one app with webhooks configured, Edusign uses the Client Secret from the specific app that has the webhook configured. This ensures each app has its own unique signature verification.
Why not API Keys? API Keys are designed for authentication when making requests TO Edusign's API. Client Secrets are specifically designed for verifying that requests are FROM Edusign to your application.
Edusign libraryYou can also use the Edusign library to retrieve the HMAC and compare it with the Client Secret of your app. You can retrieve our library here.
Example Verification Process
// Get the signature from the webhook headers
const signature = req.headers['x-edusign-hmac'];
// Your app's Client Secret (not API Key)
const clientSecret = 'your-app-client-secret';
// Verify the webhook authenticity
try {
// Use Edusign library or implement your own HMAC verification
const isValid = verifyEdusignWebhook(req.body, signature, clientSecret);
if (isValid) {
// Process the webhook safely
console.log('Webhook verified successfully');
} else {
// Reject invalid webhook
return res.status(401).json({ error: 'Invalid signature' });
}
} catch (error) {
return res.status(401).json({ error: 'Verification failed' });
}Generating Webhooks
Webhooks can be generated and managed directly within the Edusign admin settings page. Navigate to the "Settings" section in your Edusign admin dashboard, then select "Webhooks" to configure and generate new webhooks for your applications.
Important: Make sure to use the Client Secret from your Edusign App (not your API Key) for webhook HMAC verification. The Client Secret is specifically designed for this security purpose.
Quick Start Checklist
To set up webhooks securely:
- โ Create an Edusign App
- โ Note your Client Secret (for HMAC verification)
- โ Note your API Key (for making API requests)
- โ Configure your webhook endpoint URL in Edusign settings
- โ Implement HMAC verification using your Client Secret
- โ Test your webhook with a sample event
Webhook payloads reference
Every webhook is sent as a POST request to your registered URL. All payloads share the same base structure:
{
"type": "on_student_sign",
"appId": "app-marketplace-id-or-null"
}Please note that this list might not be exhaustive, More webhooks might be available directly in Edusign settings.
| Field | Type | Description |
|---|---|---|
| type | string | The webhook event type (see tables below) |
| appId | string or null | The App Marketplace ID if the webhook is registered by an app, null for school-level webhooks |
HTTP headers
Every webhook POST includes these headers:
| Header | Description |
|---|---|
| x-edusign-webhook-type | The event type (e.g. on_student_sign) |
| x-edusign-school-id | The school ID that triggered the webhook |
| x-edusign-app-id | The App Marketplace ID (empty for school webhooks) |
| x-edusign-lang | Language code (default: fr) |
| x-edusign-hmac | HMAC-SHA256 signature (app webhooks only) |
| x-edusign-client-id | App client ID (app webhooks only) |
Students
on_student_sign
Fired when a student signs an attendance sheet.
{
"type": "on_student_sign",
"appId": null,
"student": {
"FIRSTNAME": "Jean",
"LASTNAME": "Dupont",
"EMAIL": "[email protected]",
"ID": "student-uuid",
"API_ID": "external-api-id"
},
"course": {
"ID": "course-uuid",
"NAME": "Mathematics 101",
"START": "2026-03-26T09:00:00+02:00",
"END": "2026-03-26T12:00:00+02:00",
"API_ID": "external-api-id"
},
"signature": {
"state": true,
"timestamp": "2026-03-26T09:15:00+02:00"
}
}on_student_created
Fired when a student is created.
{
"type": "on_student_created",
"appId": null,
"studentId": "student-uuid"
}on_student_edited
Fired when a student record is modified.
{
"type": "on_student_edited",
"appId": null,
"studentId": "student-uuid"
}on_student_deleted
Fired when a student is deleted.
{
"type": "on_student_deleted",
"appId": null,
"studentId": "student-uuid"
}on_student_unremoved
Fired when a deleted student is restored.
{
"type": "on_student_unremoved",
"appId": null,
"studentId": "student-uuid"
}Teachers
on_teacher_sign
Fired when a teacher signs an attendance sheet.
{
"type": "on_teacher_sign",
"appId": null,
"teacher": {
"FIRSTNAME": "Marie",
"LASTNAME": "Martin",
"EMAIL": "[email protected]",
"ID": "teacher-uuid",
"API_ID": "external-api-id"
},
"course": {
"ID": "course-uuid",
"NAME": "Mathematics 101",
"START": "2026-03-26T09:00:00+02:00",
"END": "2026-03-26T12:00:00+02:00",
"API_ID": "external-api-id"
},
"signature": {
"state": true,
"timestamp": "2026-03-26T09:05:00+02:00"
}
}on_teacher_created
Fired when a teacher is created.
{
"type": "on_teacher_created",
"appId": null,
"teacherId": "teacher-uuid"
}on_teacher_edited
Fired when a teacher record is modified.
{
"type": "on_teacher_edited",
"appId": null,
"teacherId": "teacher-uuid"
}on_teacher_deleted
Fired when a teacher is deleted.
{
"type": "on_teacher_deleted",
"appId": null,
"teacherId": "teacher-uuid"
}on_teacher_unremoved
Fired when a deleted teacher is restored.
{
"type": "on_teacher_unremoved",
"appId": null,
"teacherId": "teacher-uuid"
}Groups
on_group_created
Fired when a group is created.
{
"type": "on_group_created",
"appId": null,
"groupId": "group-uuid"
}on_group_edited
Fired when a group is modified.
{
"type": "on_group_edited",
"appId": null,
"groupId": "group-uuid"
}on_group_deleted
Fired when a group is deleted.
{
"type": "on_group_deleted",
"appId": null,
"groupId": "group-uuid"
}on_group_unremoved
Fired when a deleted group is restored.
{
"type": "on_group_unremoved",
"appId": null,
"groupId": "group-uuid"
}Courses
on_course_created
Fired when a course is created.
{
"type": "on_course_created",
"appId": null,
"courseId": "course-uuid"
}on_course_edited
Fired when a course is modified.
{
"type": "on_course_edited",
"appId": null,
"courseId": "course-uuid"
}on_course_deleted
Fired when a course is deleted.
{
"type": "on_course_deleted",
"appId": null,
"courseId": "course-uuid"
}on_course_locked
Fired when a course is locked (attendance sheet generated/archived).
{
"type": "on_course_locked",
"appId": null,
"courseId": "course-uuid",
"ATTENDANCE_LIST_GENERATED": "https://s3.amazonaws.com/..."
}on_course_start (scheduled)
Fired when a course's scheduled start time arrives.
{
"type": "on_course_start",
"appId": null,
"courseId": "course-uuid"
}on_course_end (scheduled)
Fired when a course's scheduled end time arrives.
{
"type": "on_course_end",
"appId": null,
"courseId": "course-uuid"
}Documents
on_document_completed
Fired when a document finishes signing/generation.
{
"type": "on_document_completed",
"appId": null,
"documentId": "document-uuid",
"DOCUMENT_URL": "https://s3.amazonaws.com/.../document.pdf",
"DOCUMENT_PROOF_URL": "https://s3.amazonaws.com/.../proof.pdf"
}on_document_participant_signed
Fired when a participant signs a document.
{
"type": "on_document_participant_signed",
"appId": null,
"documentId": "document-uuid",
"participant": {
"type": "STUDENT",
"id": "participant-uuid"
}
}The participant.type field can be "ADMIN", "STUDENT", or "TEACHER".
Surveys
on_survey_answer
Fired when a student answers a survey.
{
"type": "on_survey_answer",
"appId": null,
"surveyId": "survey-uuid",
"studentId": "student-uuid"
}Absences
on_justified_absence
Fired when an absence is justified.
{
"type": "on_justified_absence",
"appId": null,
"absence": {
"ID": "absence-uuid",
"TYPE": 1,
"COMMENT": "Medical appointment",
"COURSE_ID": "course-uuid",
"STUDENT_ID": "student-uuid",
"SCHOOL_ID": "school-uuid",
"FILE_URL": "https://...",
"API_ID": "external-api-id",
"API_TYPE": "connector-name",
"DATE_CREATION": "2026-03-26T10:00:00+02:00",
"START": "2026-03-26T09:00:00+02:00",
"END": "2026-03-26T12:00:00+02:00",
"SOURCE": "school",
"REQUEST_DATE": "2026-03-25T14:00:00+02:00"
}
}The absence object contains all absence fields except FILE and fileType which are stripped before sending.
on_request_justified_absence
Fired when a student requests an absence justification.
{
"type": "on_request_justified_absence",
"appId": null,
"absenceRequest": {
"ID": "absence-request-uuid",
"TYPE": 1,
"STUDENT_ID": "student-uuid",
"SCHOOL_ID": "school-uuid",
"FILE_URL": "https://...",
"STATUS": 0,
"COMMENT": "Doctor visit",
"DATE_CREATED": "2026-03-26T10:00:00+02:00",
"START": "2026-03-26T09:00:00+02:00",
"END": "2026-03-26T12:00:00+02:00"
}
}Note the field is absenceRequest (not absence). The object contains all absence request fields except FILE, fileType, and rgpdChecked which are stripped before sending.
on_abscence_report_generated
Fired when an absence report PDF is generated.
{
"type": "on_abscence_report_generated",
"appId": null,
"studentId": "student-uuid",
"externalId": "external-recipient-id",
"pdfUrl": "https://s3.amazonaws.com/.../report.pdf"
}Email
on_email_invalid
Fired when an email address is detected as invalid (bounce/spam).
{
"type": "on_email_invalid",
"appId": null,
"email": "[email protected]",
"invalid_type": "hard",
"invalid_reason": "bounce"
}Zoom
on_zoom_meeting_created
Fired when a Zoom meeting is created for a course.
{
"type": "on_zoom_meeting_created",
"appId": null,
"COURSE_ID": "course-uuid",
"meeting": {
"ID": "zoom-meeting-id",
"URL": "https://zoom.us/j/123456",
"START": "2026-03-26T09:00:00+02:00",
"END": "2026-03-26T12:00:00+02:00"
}
}Note: this webhook uses COURSE_ID (uppercase) instead of courseId.
App Marketplace
on_app_parameters_changed
Fired when an app's configuration parameters are changed.
{
"type": "on_app_parameters_changed",
"appId": "app-marketplace-uuid",
"parameters": {
"customParam1": "value1",
"customParam2": true
}
}The parameters object contains the app's custom configuration as key-value pairs.
cron_midnight
Automatically fired every day at 00:07 UTC. Allows apps to have a daily cron trigger from Edusign.
{
"type": "cron_midnight",
"appId": null
}Summary table
| Event | Type string | Key payload fields |
|---|---|---|
| Student signs | on_student_sign | student, course, signature |
| Student created | on_student_created | studentId |
| Student edited | on_student_edited | studentId |
| Student deleted | on_student_deleted | studentId |
| Student restored | on_student_unremoved | studentId |
| Teacher signs | on_teacher_sign | teacher, course, signature |
| Teacher created | on_teacher_created | teacherId |
| Teacher edited | on_teacher_edited | teacherId |
| Teacher deleted | on_teacher_deleted | teacherId |
| Teacher restored | on_teacher_unremoved | teacherId |
| Group created | on_group_created | groupId |
| Group edited | on_group_edited | groupId |
| Group deleted | on_group_deleted | groupId |
| Group restored | on_group_unremoved | groupId |
| Course created | on_course_created | courseId |
| Course edited | on_course_edited | courseId |
| Course deleted | on_course_deleted | courseId |
| Course locked | on_course_locked | courseId, ATTENDANCE_LIST_GENERATED |
| Course starts | on_course_start | courseId |
| Course ends | on_course_end | courseId |
| Document completed | on_document_completed | documentId, DOCUMENT_URL, DOCUMENT_PROOF_URL |
| Document participant signed | on_document_participant_signed | documentId, participant |
| Survey answered | on_survey_answer | surveyId, studentId |
| Absence justified | on_justified_absence | absence |
| Absence justification requested | on_request_justified_absence | absenceRequest |
| Absence report generated | on_abscence_report_generated | studentId, externalId, pdfUrl |
| Email invalid | on_email_invalid | email, invalid_type, invalid_reason |
| Zoom meeting created | on_zoom_meeting_created | COURSE_ID, meeting |
| App parameters changed | on_app_parameters_changed | parameters |
| Daily cron | cron_midnight | (no additional fields) |
Notes
- Dates are in the school's configured timezone (not UTC), formatted as ISO-8601.
- on_zoom_meeting_created uses COURSE_ID (uppercase) โ this is inconsistent with other course webhooks that use courseId.
- on_abscence_report_generated has a typo in the event name (abscence instead of absence) โ this is kept for backward compatibility.
- The meeting object in on_zoom_meeting_created is passed through as-is from Zoom โ the exact fields may vary but always include ID, URL, START, END.
Updated 14 days ago
