Webhooks
Integration with webhooks allows an organization to subscribe one or more endpoints to receive confirmations of different events. They can be used to update your databases, inventory system and the like. Webhooks are set up for each partnership that the organization has.
Data is sent using the HTTP POST
method. These endpoints will not be visible to end users. Its sole purpose is to communicate the different integrated backend systems.
On each endpoint, you must capture the information necessary to update or store according to your use case and depending on the programming language you use.
Considerations
- The information is sent using the method
POST
inJSON
format. - The authentication methods that the endpoint can use are Basic authentication and Bearer authentication. Credentials cannot auto-rotate or be temporary.
- Only webhooks that use HTTPS (SSL) are accepted.
- Communication between Leah and the webhook endpoint is server-to-server. It is transparent to the end user.
- Endpoints must respond with an HTTP status code between
200
and299
upon success. Any other code will be considered failed. - In the event that a request is unsuccessful, Leah's system will attempt to send the information up to two more times.
Event triggers
The events that can trigger a webhook are the following:
You can use a single endpoint that handles all events, or use different endpoints for different triggers.
User registered event
This event triggers a request when a user joins the organization. This linking occurs in two ways: When the user redeems a partnership code or when the user redeems a license key. In both cases, the request is sent with the information of the user who linked.
- Request
- Body
- Variables
The POST
request send when a user registers looks like this:
POST / HTTP/1.1
Host: example.com
User-Agent: axios/1.6.2
Content-Length: 712
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, compress, deflate, br
Authorization: Basic bXlfdXNlcjpteV9wYXNz
Content-Type: application/json
X-Forwarded-For: 0.0.0.0
X-Forwarded-Host: example.com
X-Forwarded-Proto: https
{"event":"USER_REGISTERED","user":{"id":"65e9c4884805c146b5770c61","personalInformation":{"email":"johndoe@example.com","familyName":"Doe","givenName":"John","phoneNumber":"+573334445555","picture":"https://cdn.example.com/pictures/profile.jpg","customFields":[{"name":"doc_type","value":"CC"},{"name":"doc_number","value":"1048222222"}]}},"partner":{"id":"662fc3c33eb47f6dcb97c71e","name":"Test Partner","code":null},"date":"2024-03-07T13:43:40.674Z","externalIds":["random-external-id"]}
The body of the request sent when a user registers looks like this:
{
"event": "USER_REGISTERED",
"user": {
"id": "65e9c4884805c146b5770c61",
"personalInformation": {
"email": "johndoe@example.com",
"familyName": "Doe",
"givenName": "John",
"phoneNumber": "+573334445555",
"picture": "https://cdn.example.com/pictures/profile.jpg",
"customFields": [
{
"name": "doc_type",
"value": "CC"
},
{
"name": "doc_number",
"value": "1048222222"
}
]
}
},
"partner": {
"id": "662fc3c33eb47f6dcb97c71e",
"name": "Test Partner",
"code": null
}
"date": "2024-03-07T13:43:40.674Z",
"externalIds": ["random-external-id"]
}
The variables sent in the body of the request when a user registers are the following:
Field | Type | Required | Description |
---|---|---|---|
event | Alphanumeric | Yes | Event type. When the request is triggered by the user registration, it always has the value USER_REGISTERED . |
date | Alphanumeric (date in ISO 8601 definition: YYYY-MM-DDTHH:mm:ss.sssZ ) | Yes | Date when the user was registered. |
user.id | Alphanumeric | Yes | Unique ID of the user registered |
user.personalInformation.email | Alphanumeric (email address as defined in HTML spec) | Yes | User email address. |
user.personalInformation.familyName | Alphanumeric | Yes | User's last name. |
user.personalInformation.givenName | Alphanumeric | Yes | User's given name. |
user.personalInformation.phoneNumber | Alphanumeric (Phone number as defined in E.164 spec) | No | User's phone number. |
user.personalInformation.picture | Alphanumeric (URL) | No | User's profile picture URL. |
user.personalInformation.customFields | Array | No | User's additional fields. If the user belongs to an external identity provider (SAML integration), additional fields may have been defined; If that is the case, they will be listed in this field. |
user.personalInformation.customFields[].name | Alphanumeric | No | Name of the custom field. |
user.personalInformation.customFields[].value | Alphanumeric | No | Value of the custom field. |
partner.id | Alphanumeric | Yes | Unique ID of the partnership. |
partner.name | Alphanumeric | Yes | Name of the partnership. |
partner.code | Alphanumeric | No | Partnership code. It only applies to B2B partnerships. |
externalIds | Array (Alphanumeric) | No | If the user has registered using a license key, the key externalId used to create the license will be included in this list. If it does not include a value, it means that the user registered using a partnership code. |
Onboarding finished event
This event triggers a request when a user completes their profile, self-assessment, and learning goal information. The information sent includes all of this new information.
- Request
- Body
- Variables
The POST
request sent when a user finish the onboarding looks like this:
POST / HTTP/1.1
Host: example.com
User-Agent: axios/1.6.2
Content-Length: 905
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, compress, deflate, br
Authorization: Basic bXlfdXNlcjpteV9wYXNz
Content-Type: application/json
X-Forwarded-For: 0.0.0.0
X-Forwarded-Host: example.com
X-Forwarded-Proto: https
{"event":"ONBOARDING_FINISHED","externalIds":["random-external-id"],"date":"2024-09-02T14:31:28.757Z","user":{"id":"65e9c4884805c146b5770c61","personalInformation":{"email":"johndoe@example.com","familyName":"Doe","givenName":"John","phoneNumber":"+573334445555","picture":"https://cdn.example.com/pictures/profile.jpg","customFields":[{"name":"doc_type","value":"CC"},{"name":"doc_number","value":"1048222222"}]}},"partner":{"id":"662fc3c33eb47f6dcb97c71e","name":"Test Partner","code":null},"perception":{"countryCode":"CO","regionName":"Atlantico","proficiency":{"grammarAndVocabulary":5,"readingComprehension":4,"listeningComprehension":3,"writing":2,"speaking":1},"goal":"B2","timeStudyingEnglish":"BETWEEN_3_AND_5_YEARS","whyIsLearningEnglish":"INCREASE_YOUR_KNOWLEDGE","topicsOfInterest":["General English","Technology"]}}
The body of the request sent when a user finished the onboarding looks like this:
{
"event": "ONBOARDING_FINISHED",
"externalIds": ["random-external-id"],
"date": "2024-09-02T14:31:28.757Z",
"user": {
"id": "65e9c4884805c146b5770c61",
"personalInformation": {
"email": "johndoe@example.com",
"familyName": "Doe",
"givenName": "John",
"phoneNumber": "+573334445555",
"picture": "https://cdn.example.com/pictures/profile.jpg",
"customFields": [
{
"name": "doc_type",
"value": "CC"
},
{
"name": "doc_number",
"value": "1048222222"
}
]
}
},
"partner": {
"id": "662fc3c33eb47f6dcb97c71e",
"name": "Test Partner",
"code": null
},
"perception": {
"countryCode": "CO",
"regionName": "Atlantico",
"proficiency": {
"grammarAndVocabulary": 5,
"readingComprehension": 4,
"listeningComprehension": 3,
"writing": 2,
"speaking": 1
},
"goal": "B2",
"timeStudyingEnglish": "BETWEEN_3_AND_5_YEARS",
"whyIsLearningEnglish": "INCREASE_YOUR_KNOWLEDGE",
"topicsOfInterest": ["General English", "Technology"]
}
}
The variables sent in the body of the request when a user finished the onboarding are the following:
Field | Type | Required | Description |
---|---|---|---|
event | Alphanumeric | Yes | Event type. When the request is triggered by the user onboarding, it always has the value ONBOARDING_FINISHED . |
date | Alphanumeric (date in ISO 8601 definition: YYYY-MM-DDTHH:mm:ss.sssZ ) | Yes | Date when user finished the onboarding. |
perception.countryCode | Alphanumeric (2 letters alpha code as defined in ISO 3166-1 alpha-2) | Yes | User's country code. |
perception.regionName | Alphanumeric | Yes | User's state/region. |
perception.proficiency.grammarAndVocabulary | Number (between 1 and 5) | No | User self-assessment of grammar and vocabulary skills. |
perception.proficiency.readingComprehension | Number (between 1 and 5) | No | User self-assessment of reading comprehension skills. |
perception.proficiency.listeningComprehension | Number (between 1 and 5) | No | User self-assessment of listening skills. |
perception.proficiency.writing | Number (between 1 and 5) | No | User self-assessment of writing skill. |
perception.proficiency.speaking | Number (between 1 and 5) | No | User self-assessment of speaking skills. |
perception.goal | Alphanumeric (CEFR english level) | No | User self-imposed learning goal. |
perception.timeStudyingEnglish | Alphanumeric | No | User's time studying english. |
perception.whyIsLearningEnglish | Alphanumeric | No | User's reason why is english. |
perception.topicsOfInterest | Array (Alphanumeric) | No | User's topics of interest in their english learning. |
user.id | Alphanumeric | Yes | Unique ID of the user registered. |
user.personalInformation.email | Alphanumeric (email address as defined in HTML spec) | Yes | User email address. |
user.personalInformation.familyName | Alphanumeric | Yes | User's last name. |
user.personalInformation.givenName | Alphanumeric | Yes | User's given name. |
user.personalInformation.phoneNumber | Alphanumeric (Phone number as defined in E.164 spec) | No | User's phone number. |
user.personalInformation.picture | Alphanumeric (URL) | No | User's profile picture URL. |
user.personalInformation.customFields | Array | No | User's additional fields. If the user belongs to an external identity provider (SAML integration), additional fields may have been defined; If that is the case, they will be listed in this field. |
user.personalInformation.customFields[].name | Alphanumeric | No | Name of the custom field. |
user.personalInformation.customFields[].value | Alphanumeric | No | Value of the custom field. |
partner.id | Alphanumeric | Yes | Unique ID of the partnership. |
partner.name | Alphanumeric | Yes | Name of the partnership. |
partner.code | Alphanumeric | No | Partnership code. It only applies to B2B partnerships. |
externalIds | Array (Alphanumeric) | No | If the user has registered using a license key, the key externalId used to create the license will be included in this list. If it does not include a value, it means that the user registered using a partnership code. |
Placement test finished event
This event triggers a request when a user linked to the organization completes a placement test. The information sent includes the user's results on the test.
- Request
- Body
- Variables
The POST
request sent when a user finishes a placement test looks like this:
POST / HTTP/1.1
Host: example.com
User-Agent: axios/1.6.2
Content-Length: 1079
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, compress, deflate, br
Authorization: Basic bXlfdXNlcjpteV9wYXNz
Content-Type: application/json
X-Forwarded-For: 0.0.0.0
X-Forwarded-Host: example.com
X-Forwarded-Proto: https
{"event":"PLACEMENT_TEST_FINISHED","test":{"id":"65e9c74f4805c146b5770d4c","start":"2024-03-07T13:55:27.709Z","end":"2024-03-07T13:56:27.846Z","questionCount":36,"result":{"level":"A1","sublevel":"A1.2","score":7.61,"languageScore":2.65,"readingScore":24.26,"listeningScore":26.83,"pdf":"https://static.leahapp.com/certificates/diagnostic/fee7d137-8119-4ee7-b4f3.pdf"}},"user":{"id":"65e9c4884805c146b5770c61","personalInformation":{"email":"johndoe@example.com","familyName":"Doe","givenName":"John","phoneNumber":"+573334445555","picture":"https://cdn.example.com/pictures/profile.jpg","customFields":[{"name":"doc_type","value":"CC"},{"name":"doc_number","value":"1048222222"}]}},"partner":{"id":"662fc3c33eb47f6dcb97c71e","name":"Test Partner","code":null},"date":"2024-03-07T13:56:27.846Z","externalIds":["random-external-id"]}
The body of the request sent when a user finishes a placement test looks like this:
{
"event": "PLACEMENT_TEST_FINISHED",
"test": {
"id": "65e9c74f4805c146b5770d4c",
"start": "2024-03-07T13:55:27.709Z",
"end": "2024-03-07T13:56:27.846Z",
"questionCount": 36,
"hasProctoring": true,
"proctoringWarning": false,
"result": {
"level": "A1",
"sublevel": "A1.2",
"score": 7.61,
"languageScore": 2.65,
"readingScore": 24.26,
"listeningScore": 26.83,
"pdf": "https://static.leahapp.com/certificates/diagnostic/fee7d137-8119-4ee7-b4f3.pdf"
}
},
"user": {
"id": "65e9c4884805c146b5770c61",
"personalInformation": {
"email": "johndoe@example.com",
"familyName": "Doe",
"givenName": "John",
"phoneNumber": "+573334445555",
"picture": "https://cdn.example.com/pictures/profile.jpg",
"customFields": [
{
"name": "doc_type",
"value": "CC"
},
{
"name": "doc_number",
"value": "1048222222"
}
]
}
},
"partner": {
"id": "662fc3c33eb47f6dcb97c71e",
"name": "Test Partner",
"code": null
},
"date": "2024-03-07T13:56:27.846Z",
"externalIds": ["random-external-id"]
}
The variables sent in the body of the request when a user finishes a placement test are the following:
Field | Type | Required | Description |
---|---|---|---|
event | Alphanumeric | Yes | Event type. When the request is triggered by the user finishing a placement test, it always has the value PLACEMENT_TEST_FINISHED . |
date | Alphanumeric (date in ISO 8601 definition: YYYY-MM-DDTHH:mm:ss.sssZ ) | Yes | Date when user finished the placement test. |
test.id | Alphanumeric | Yes | Unique ID of the placement test. |
test.start | Alphanumeric (date in ISO 8601 definition: YYYY-MM-DDTHH:mm:ss.sssZ ) | Yes | Placement test start date. |
test.end | Alphanumeric (date in ISO 8601 definition: YYYY-MM-DDTHH:mm:ss.sssZ ) | Yes | Placement test end date. |
test.questionCount | Number (Integer) | Yes | Number of test questions. |
test.hasProctoring | Boolean | Yes | If the partner is using the proctoring this field will be true . Otherwise, it will be false . |
test.proctoringWarning | Boolean | No | If the partner is using the proctoring system and possible fraud is detected by the user who took the test, this field will be true . Otherwise, it will be false . |
test.result.level | Alphanumeric | Yes | Level obtained by the user in the test. |
test.result.sublevel | Alphanumeric | Yes | Sub-level obtained by the user in the test. |
test.result.score | Number (Floating point value between 0 and 100) | Yes | General score obtained by the user in the test. |
test.result.languageScore | Number (Floating point value between 0 and 100) | Yes | Score obtained by the user in the test for language use skill. |
test.result.readingScore | Number (Floating point value between 0 and 100) | Yes | Score obtained by the user in the test for reading skill. |
test.result.listeningScore | Number (Floating point value between 0 and 100) | Yes | Score obtained by the user in the test for listening skill. |
test.result.pdf | Alphanumeric (URL) | No | URL of the report in PDF format of the result obtained by the user in the test. |
user.id | Alphanumeric | Yes | Unique ID of the user registered. |
user.personalInformation.email | Alphanumeric (email address as defined in HTML spec) | Yes | User email address. |
user.personalInformation.familyName | Alphanumeric | Yes | User's last name. |
user.personalInformation.givenName | Alphanumeric | Yes | User's given name. |
user.personalInformation.phoneNumber | Alphanumeric (Phone number as defined in E.164 spec) | No | User's phone number. |
user.personalInformation.picture | Alphanumeric (URL) | No | User's profile picture URL. |
user.personalInformation.customFields | Array | No | User's additional fields. If the user belongs to an external identity provider (SAML integration), additional fields may have been defined; If that is the case, they will be listed in this field. |
user.personalInformation.customFields[].name | Alphanumeric | No | Name of the custom field. |
user.personalInformation.customFields[].value | Alphanumeric | No | Value of the custom field. |
partner.id | Alphanumeric | Yes | Unique ID of the partnership. |
partner.name | Alphanumeric | Yes | Name of the partnership. |
partner.code | Alphanumeric | No | Partnership code. It only applies to B2B partnerships. |
externalIds | Array (Alphanumeric) | No | If the user has registered using a license key, the key externalId used to create the license will be included in this list. If it does not include a value, it means that the user registered using a partnership code. |
Speaking test finished event
This event triggers a request when a user linked to the organization completes a speaking test. The information sent includes the user's results on the test.
- Request
- Body
- Variables
The POST
request sent when a user finishes a speaking test looks like this:
POST / HTTP/1.1
Host: example.com
User-Agent: axios/1.6.2
Content-Length: 1110
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, compress, deflate, br
Authorization: Basic bXlfdXNlcjpteV9wYXNz
Content-Type: application/json
X-Forwarded-For: 0.0.0.0
X-Forwarded-Host: example.com
X-Forwarded-Proto: https
{"event":"SPEAKING_TEST_FINISHED","test":{"id":"65e9c9384805c146b57710bc","start":"2024-03-07T14:03:36.894Z","end":"2024-03-07T14:05:45.078Z","questionCount":3,"isValid":true,"result":{"level":"Pre-A1","sublevel":"Pre-A1","score":42.87,"vocabularyScore":41.49,"fluencyScore":39.54,"pronunciationScore":59.31,"grammarScore":38.86,"pdf":"https://static.leahapp.com/certificates/speaking/e8b40139-c654-4fd3-bb14.pdf"}},"user":{"id":"65e9c4884805c146b5770c61","personalInformation":{"email":"johndoe@example.com","familyName":"Doe","givenName":"John","phoneNumber":"+573334445555","picture":"https://cdn.example.com/pictures/profile.jpg","customFields":[{"name":"doc_type","value":"CC"},{"name":"doc_number","value":"1048222222"}]}},"partner":{"id":"662fc3c33eb47f6dcb97c71e","name":"Test Partner","code":null},"date":"2024-03-07T14:05:45.078Z","externalIds":["random-external-id"]}
The body of the request sent when a user finishes a speaking test looks like this:
{
"event": "SPEAKING_TEST_FINISHED",
"test": {
"id": "65e9c9384805c146b57710bc",
"start": "2024-03-07T14:03:36.894Z",
"end": "2024-03-07T14:05:45.078Z",
"questionCount": 3,
"isValid": true,
"hasProctoring": true,
"proctoringWarning": false,
"result": {
"level": "Pre-A1",
"sublevel": "Pre-A1",
"score": 42.87,
"vocabularyScore": 41.49,
"fluencyScore": 39.54,
"pronunciationScore": 59.31,
"grammarScore": 38.86,
"pdf": "https://static.leahapp.com/certificates/speaking/e8b40139-c654-4fd3-bb14.pdf"
}
},
"user": {
"id": "65e9c4884805c146b5770c61",
"personalInformation": {
"email": "johndoe@example.com",
"familyName": "Doe",
"givenName": "John",
"phoneNumber": "+573334445555",
"picture": "https://cdn.example.com/pictures/profile.jpg",
"customFields": [
{
"name": "doc_type",
"value": "CC"
},
{
"name": "doc_number",
"value": "1048222222"
}
]
}
},
"partner": {
"id": "662fc3c33eb47f6dcb97c71e",
"name": "Test Partner",
"code": null
},
"date": "2024-03-07T14:05:45.078Z",
"externalIds": ["random-external-id"]
}
The variables sent in the body of the request when a user finishes a speaking test are the following:
Field | Type | Required | Description |
---|---|---|---|
event | Alphanumeric | Yes | Event type. When the request is triggered by the user finishing a speaking test, it always has the value SPEAKING_TEST_FINISHED . |
date | Alphanumeric (date in ISO 8601 definition: YYYY-MM-DDTHH:mm:ss.sssZ ) | Yes | Date when user finished the speaking test. |
test.id | Alphanumeric | Yes | Unique ID of the speaking test. |
test.start | Alphanumeric (date in ISO 8601 definition: YYYY-MM-DDTHH:mm:ss.sssZ ) | Yes | Speaking test start date. |
test.end | Alphanumeric (date in ISO 8601 definition: YYYY-MM-DDTHH:mm:ss.sssZ ) | Yes | Speaking test end date. |
test.questionCount | Number (Integer) | Yes | Number of test questions. |
test.isValid | Boolean | Yes | Specify if the speaking test is valid. A speaking test is valid when the user met all guidelines (duration, context, etc.) and speech recognition was satisfactory for all answers. |
test.hasProctoring | Boolean | Yes | If the partner is using the proctoring this field will be true . Otherwise, it will be false . |
test.proctoringWarning | Boolean | No | If the partner is using the proctoring system and possible fraud is detected by the user who took the test, this field will be true . Otherwise, it will be false . |
test.result.level | Alphanumeric | Yes | Level obtained by the user in the test. |
test.result.sublevel | Alphanumeric | Yes | Sub-level obtained by the user in the test. |
test.result.score | Number (Floating point value between 0 and 100) | Yes | General score obtained by the user in the test. |
test.result.vocabularyScore | Number (Floating point value between 0 and 100) | Yes | Vocabulary score obtained by the user in the test. |
test.result.fluencyScore | Number (Floating point value between 0 and 100) | Yes | Fluency score obtained by the user in the test. |
test.result.pronunciationScore | Number (Floating point value between 0 and 100) | Yes | Pronunciation score obtained by the user in the test. |
test.result.grammarScore | Number (Floating point value between 0 and 100) | Yes | Grammar score obtained by the user in the test. |
test.result.pdf | Alphanumeric (URL) | No | URL of the report in PDF format of the result obtained by the user in the test. |
user.id | Alphanumeric | Yes | Unique ID of the user. |
user.personalInformation.email | Alphanumeric (email address as defined in HTML spec) | Yes | User email address. |
user.personalInformation.familyName | Alphanumeric | Yes | User's last name. |
user.personalInformation.givenName | Alphanumeric | Yes | User's given name. |
user.personalInformation.phoneNumber | Alphanumeric (Phone number as defined in E.164 spec) | No | User's phone number. |
user.personalInformation.picture | Alphanumeric (URL) | No | User's profile picture URL. |
user.personalInformation.customFields | Array | No | User's additional fields. If the user belongs to an external identity provider (SAML integration), additional fields may have been defined; If that is the case, they will be listed in this field. |
user.personalInformation.customFields[].name | Alphanumeric | No | Name of the custom field. |
user.personalInformation.customFields[].value | Alphanumeric | No | Value of the custom field. |
partner.id | Alphanumeric | Yes | Unique ID of the partnership. |
partner.name | Alphanumeric | Yes | Name of the partnership. |
partner.code | Alphanumeric | No | Partnership code. It only applies to B2B partnerships. |
externalIds | Array (Alphanumeric) | No | If the user has registered using a license key, the key externalId used to create the license will be included in this list. If it does not include a value, it means that the user registered using a partnership code. |
Overall level event
This event triggers a request when a user completes a speaking test or a placement test and their overall level is calculated.
- Request
- Body
- Variables
The POST
request sent when the overall level is calculated looks like this:
POST / HTTP/1.1
Host: example.com
User-Agent: axios/1.6.8
Content-Length: 972
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, compress, deflate, br
Authorization: Basic bXlfdXNlcjpteV9wYXNz
Content-Type: application/json
X-Forwarded-For: 190.61.42.42
X-Forwarded-Host: example.com
X-Forwarded-Proto: https
{"event":"OVERALL_LEVEL","overall":{"score":27.4,"level":"Level 1","sublevel":"Sublevel 1.1"},"speakingTest":{"id":"663d0406c293f8d5895f639c","start":"2024-05-09T17:12:38.616Z","end":"2024-05-09T17:15:20.238Z","questionCount":3,"isValid":false,"result":{"level":"Pre-A1","sublevel":"Pre-A1","score":0,"vocabularyScore":0,"fluencyScore":0,"pronunciationScore":0,"grammarScore":0}},"placementTest":{"id":"6647c08136ec32eb28152cf6","start":"2024-05-17T20:39:29.218Z","end":"2024-05-17T20:41:09.135Z","questionCount":36,"result":{"level":"Level 1","sublevel":"Sublevel 1.1","score":21.75,"languageScore":23.37,"readingScore":21.09,"listeningScore":31.68}},"user":{"id":"660b2921fd05f52867c408e1","personalInformation":{"email":"johndoe@example.com","familyName":"Doe","givenName":"John","phoneNumber":"+573334445555","customFields":[]}},"partner":{"id":"6408f36388f7f41b188288a6","name":"Test Partner","code":"0000"},"date":"2024-05-17T20:41:23.238Z","externalIds":[]}
The body of the request sent when the level is calculated looks like this:
{
"event": "OVERALL_LEVEL",
"overall": {
"score": 27.4,
"level": "Level 1",
"sublevel": "Sublevel 1.1"
},
"speakingTest": {
"id": "663d0406c293f8d5895f639c",
"start": "2024-05-09T17:12:38.616Z",
"end": "2024-05-09T17:15:20.238Z",
"questionCount": 3,
"isValid": false,
"hasProctoring": true,
"proctoringWarning": false,
"result": {
"level": "Pre-A1",
"sublevel": "Pre-A1",
"score": 0,
"vocabularyScore": 0,
"fluencyScore": 0,
"pronunciationScore": 0,
"grammarScore": 0
}
},
"placementTest": {
"id": "6647c08136ec32eb28152cf6",
"start": "2024-05-17T20:39:29.218Z",
"end": "2024-05-17T20:41:09.135Z",
"questionCount": 36,
"hasProctoring": true,
"proctoringWarning": false,
"result": {
"level": "Level 1",
"sublevel": "Sublevel 1.1",
"score": 21.75,
"languageScore": 23.37,
"readingScore": 21.09,
"listeningScore": 31.68
}
},
"user": {
"id": "660b2921fd05f52867c408e1",
"personalInformation": {
"email": "johndoe@example.com",
"familyName": "Doe",
"givenName": "John",
"phoneNumber": "+573334445555",
"customFields": []
}
},
"partner": {
"id": "6408f36388f7f41b188288a6",
"name": "Pruebaleahmt",
"code": "0000"
},
"date": "2024-05-17T20:41:23.238Z",
"externalIds": []
}
Field | Type | Required | Description |
---|---|---|---|
event | Alphanumeric | Yes | Event type. When the request is triggered by the overall level calculation, it always has the value OVERALL_LEVEL . |
overall.score | Number (Floating point value between 0 and 100) | Yes | Overall score. |
overall.level | Alphanumeric | Yes | Overall level. |
overall.sublevel | Alphanumeric | Yes | Overall sublevel. |
date | Alphanumeric (date in ISO 8601 definition: YYYY-MM-DDTHH:mm:ss.sssZ ) | Yes | Date when overall level was calculated. |
placementTest.id | Alphanumeric | Yes | Unique ID of the placement test. |
placementTest.start | Alphanumeric (date in ISO 8601 definition: YYYY-MM-DDTHH:mm:ss.sssZ ) | Yes | Placement test start date. |
placementTest.end | Alphanumeric (date in ISO 8601 definition: YYYY-MM-DDTHH:mm:ss.sssZ ) | Yes | Placement test end date. |
placementTest.questionCount | Number (Integer) | Yes | Number of test questions. |
placementTest.hasProctoring | Boolean | Yes | If the partner is using the proctoring this field will be true . Otherwise, it will be false . |
placementTest.proctoringWarning | Boolean | No | If the partner is using the proctoring system and possible fraud is detected by the user who took the test, this field will be true . Otherwise, it will be false . |
placementTest.result.level | Alphanumeric | Yes | Level obtained by the user in the test. |
placementTest.result.sublevel | Alphanumeric | Yes | Sub-level obtained by the user in the test. |
placementTest.result.score | Number (Floating point value between 0 and 100) | Yes | General score obtained by the user in the test. |
placementTest.result.languageScore | Number (Floating point value between 0 and 100) | Yes | Score obtained by the user in the test for language use skill. |
placementTest.result.readingScore | Number (Floating point value between 0 and 100) | Yes | Score obtained by the user in the test for reading skill. |
placementTest.result.listeningScore | Number (Floating point value between 0 and 100) | Yes | Score obtained by the user in the test for listening skill. |
placementTest.result.pdf | Alphanumeric (URL) | No | URL of the report in PDF format of the result obtained by the user in the test. |
speakingTest.id | Alphanumeric | Yes | Unique ID of the speaking test. |
speakingTest.start | Alphanumeric (date in ISO 8601 definition: YYYY-MM-DDTHH:mm:ss.sssZ ) | Yes | Speaking test start date. |
speakingTest.end | Alphanumeric (date in ISO 8601 definition: YYYY-MM-DDTHH:mm:ss.sssZ ) | Yes | Speaking test end date. |
speakingTest.questionCount | Number (Integer) | Yes | Number of test questions. |
speakingTest.isValid | Boolean | Yes | Specify if the speaking test is valid. A speaking test is valid when the user met all guidelines (duration, context, etc.) and speech recognition was satisfactory for all answers. |
speakingTest.hasProctoring | Boolean | Yes | If the partner is using the proctoring this field will be true . Otherwise, it will be false . |
speakingTest.proctoringWarning | Boolean | No | If the partner is using the proctoring system and possible fraud is detected by the user who took the test, this field will be true . Otherwise, it will be false . |
speakingTest.result.level | Alphanumeric | Yes | Level obtained by the user in the test. |
speakingTest.result.sublevel | Alphanumeric | Yes | Sub-level obtained by the user in the test. |
speakingTest.result.score | Number (Floating point value between 0 and 100) | Yes | General score obtained by the user in the test. |
speakingTest.result.vocabularyScore | Number (Floating point value between 0 and 100) | Yes | Vocabulary score obtained by the user in the test. |
speakingTest.result.fluencyScore | Number (Floating point value between 0 and 100) | Yes | Fluency score obtained by the user in the test. |
speakingTest.result.pronunciationScore | Number (Floating point value between 0 and 100) | Yes | Pronunciation score obtained by the user in the test. |
speakingTest.result.grammarScore | Number (Floating point value between 0 and 100) | Yes | Grammar score obtained by the user in the test. |
speakingTest.result.pdf | Alphanumeric (URL) | No | URL of the report in PDF format of the result obtained by the user in the test. |
user.id | Alphanumeric | Yes | Unique ID of the user registered. |
user.personalInformation.email | Alphanumeric (email address as defined in HTML spec) | Yes | User email address. |
user.personalInformation.familyName | Alphanumeric | Yes | User's last name. |
user.personalInformation.givenName | Alphanumeric | Yes | User's given name. |
user.personalInformation.phoneNumber | Alphanumeric (Phone number as defined in E.164 spec) | No | User's phone number. |
user.personalInformation.picture | Alphanumeric (URL) | No | User's profile picture URL. |
user.personalInformation.customFields | Array | No | User's additional fields. If the user belongs to an external identity provider (SAML integration), additional fields may have been defined; If that is the case, they will be listed in this field. |
user.personalInformation.customFields[].name | Alphanumeric | No | Name of the custom field. |
user.personalInformation.customFields[].value | Alphanumeric | No | Value of the custom field. |
partner.id | Alphanumeric | Yes | Unique ID of the partnership. |
partner.name | Alphanumeric | Yes | Name of the partnership. |
partner.code | Alphanumeric | No | Partnership code. It only applies to B2B partnerships. |
externalIds | Array (Alphanumeric) | No | If the user has registered using a license key, the key externalId used to create the license will be included in this list. If it does not include a value, it means that the user registered using a partnership code. |
Example
Below are instructions for building a simple server that is capable of handle the four types of webhook events that Leah can trigger.
The example will allow the following considerations to be implemented:
- Handle authentication via "Bearer Secret Token" and "Basic HTTP". If authentication fails, respond with an HTTP status code
401 Unauthorized
. - Handle any of the different types of event.
- Validate the types of events received for each trigger, and if a valid type is not detected, respond with an HTTP status code
400 Bad request
. If it is a successful event, it responds with an HTTP status code200 Success
.
Step 1: Create server
- Node.js
Save the following block of code in a file named server.mjs
:
import http from "node:http";
const SECRET_TOKEN = "qhDm976TwG2sBZcftRLube";
const USER = "my_user";
const PASSWORD = "my_pass";
http
.createServer(function (req, res) {
const token = req.headers.authorization ?? "";
const auth = checkToken(token);
if (!auth) {
res.writeHead(401);
res.write("Unauthorized");
return res.end();
}
const chunks = [];
req.on("data", (chunk) => chunks.push(chunk));
req.on("end", () => {
const data = JSON.parse(Buffer.concat(chunks).toString());
switch (data.event) {
case "USER_REGISTERED":
console.log("User registered", JSON.stringify(data, null, 4));
break;
case "ONBOARDING_FINISHED":
console.log("Onboarding finished", JSON.stringify(data, null, 4));
break;
case "PLACEMENT_TEST_FINISHED":
console.log("Placement Test finished", JSON.stringify(data, null, 4));
break;
case "SPEAKING_TEST_FINISHED":
console.log("Speaking Test finished", JSON.stringify(data, null, 4));
break;
case "OVERALL_LEVEL":
console.log("Overall Level", JSON.stringify(data, null, 4));
break;
default:
res.writeHead(400);
res.write("Bad request");
return res.end();
}
res.writeHead(200, { "Content-Type": "application/json" });
res.write(JSON.stringify({ success: true }));
res.end();
});
})
.listen(8080);
function checkToken(token) {
const [tokenType, tokenValue] = token.split(" ");
if (tokenType === "Bearer") {
if (tokenValue === SECRET_TOKEN) return true;
} else if (tokenType === "Basic") {
const credentials = Buffer.from(tokenValue, "base64").toString();
const [user, pass] = credentials.split(":");
if (USER === user && PASSWORD === pass) return true;
}
return false;
}
The code above is for example purposes only and is not intended for use in a real environment. It contains bad practices and security issues such as including credentials directly in the code, comparing credentials without a constant time algorithm and does not perform any real operations. The only thing that is done is to print the request data in the stdout
.
Step 2: Start server
The server is started on the port 8080
by running the following command:
- Node.js
node server.mjs
Step 3: Simulate incoming requests
Requests can be made to the server using curl
. For example, to simulate the user registered event, using "Basic HTTP" authentication, you can run the following curl command:
curl --request POST \
--url http://localhost:8080/ \
--header 'Authorization: Basic bXlfdXNlcjpteV9wYXNz' \
--header 'Content-Type: application/json' \
--header 'accept: application/json' \
--data '{
"event": "USER_REGISTERED",
"user": {
"id": "65e9c4884805c146b5770c61",
"personalInformation": {
"email": "johndoe@example.com",
"familyName": "Doe",
"givenName": "John",
"phoneNumber": "+573334445555",
"picture": "https://cdn.example.com/pictures/profile.jpg",
"customFields": [
{
"name": "doc_type",
"value": "CC"
},
{
"name": "doc_number",
"value": "1048222222"
}
]
}
},
"date": "2024-03-07T13:43:40.674Z",
"externalIds": [
"random-external-id"
]
}'
The user registered event information is sent through the curl command. You can try any of the other events using the example requests discussed above.
Setting up webhooks
To set up one or more webhooks you must have an active partnership with Leah. This can be a B2B partnership, a B2B2C partnership or a Leads partnership.
- B2B2C partnership
- Leads partnership
- B2B partnership
If you have B2B2C partnership, Leah provided you with an identifier known as the partnerId
. To add a webhook that receives events triggered by this partnership users, you must send an email to the agent or developer managing your partnership with the following information:
- Specify the
partnerId
. - Endpoint URL.
- Endpoint authentication type and credentials.
- Event types that should trigger requests to the endpoint.
If you have a Leads partnership, Leah provided you with an identifier known as the partnerId
. To add a webhook that receives events triggered by this partnership users, you must send an email to the agent or developer managing your partnership with the following information:
- Specify the
partnerId
. - Endpoint URL.
- Endpoint authentication type and credentials.
- Event types that should trigger requests to the endpoint.
If you have a B2B partnership, Leah provided you with an alphanumeric code (the same one used by users to register in Leah). To add a webhook that receives events triggered by this partnership users, you must send an email to the agent or developer managing your partnership with the following information:
- Specify the partnership code.
- Endpoint URL.
- Endpoint authentication type and credentials.
- Event types that should trigger requests to the endpoint.