๐Ÿช 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:

FeatureAPI KeysClient Secret
PurposeGeneral API authenticationWebhook HMAC signature verification
QuantityUp to 5 per school1 per app
UsageAuthorization: Bearer <API_KEY>HMAC signature validation
ScopeAll API endpointsWebhook security only

Creating an Edusign App

To obtain a Client ID and Client Secret, you need to create an Edusign App:

  1. Go to your school dashboard: Private Apps
  2. Create a new app (can remain private or be published publicly after validation)
  3. 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:

  1. Retrieve the HMAC signature from the x-edusign-hmac header
  2. Use your app's Client Secret to generate your own signature
  3. 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 library

You 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:

  1. โœ… Create an Edusign App
  2. โœ… Note your Client Secret (for HMAC verification)
  3. โœ… Note your API Key (for making API requests)
  4. โœ… Configure your webhook endpoint URL in Edusign settings
  5. โœ… Implement HMAC verification using your Client Secret
  6. โœ… 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.

FieldTypeDescription
typestringThe webhook event type (see tables below)
appIdstring or nullThe App Marketplace ID if the webhook is registered by an app, null for school-level webhooks

HTTP headers

Every webhook POST includes these headers:

HeaderDescription
x-edusign-webhook-typeThe event type (e.g. on_student_sign)
x-edusign-school-idThe school ID that triggered the webhook
x-edusign-app-idThe App Marketplace ID (empty for school webhooks)
x-edusign-langLanguage code (default: fr)
x-edusign-hmacHMAC-SHA256 signature (app webhooks only)
x-edusign-client-idApp 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

EventType stringKey payload fields
Student signson_student_signstudent, course, signature
Student createdon_student_createdstudentId
Student editedon_student_editedstudentId
Student deletedon_student_deletedstudentId
Student restoredon_student_unremovedstudentId
Teacher signson_teacher_signteacher, course, signature
Teacher createdon_teacher_createdteacherId
Teacher editedon_teacher_editedteacherId
Teacher deletedon_teacher_deletedteacherId
Teacher restoredon_teacher_unremovedteacherId
Group createdon_group_createdgroupId
Group editedon_group_editedgroupId
Group deletedon_group_deletedgroupId
Group restoredon_group_unremovedgroupId
Course createdon_course_createdcourseId
Course editedon_course_editedcourseId
Course deletedon_course_deletedcourseId
Course lockedon_course_lockedcourseId, ATTENDANCE_LIST_GENERATED
Course startson_course_startcourseId
Course endson_course_endcourseId
Document completedon_document_completeddocumentId, DOCUMENT_URL, DOCUMENT_PROOF_URL
Document participant signedon_document_participant_signeddocumentId, participant
Survey answeredon_survey_answersurveyId, studentId
Absence justifiedon_justified_absenceabsence
Absence justification requestedon_request_justified_absenceabsenceRequest
Absence report generatedon_abscence_report_generatedstudentId, externalId, pdfUrl
Email invalidon_email_invalidemail, invalid_type, invalid_reason
Zoom meeting createdon_zoom_meeting_createdCOURSE_ID, meeting
App parameters changedon_app_parameters_changedparameters
Daily croncron_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.