Webhooks
La integración con "webhooks" permite suscribir uno o más "endpoints" para recibir confirmaciones de distintos eventos. Pueden usarse para actualizar sus bases de datos, sistema de inventario y similares.
Los datos son enviados utilizando el método HTTP POST
. Estos "endpoints" no estarán visibles a los usuarios finales. Su único propósito es comunicar los distintos sistemas integrados.
En cada "endpoint", debe capturar la información necesaria para actualizar o almacenar de acuerdo a su caso de uso y dependiendo del lenguaje de programación que utilice.
Consideraciones
- La información es enviada utilizando el método
POST
y en formatoJSON
. - Los métodos de autenticación que puede usar el "endpoint" son "Basic authentication" y "Bearer authentication". Las credenciales no pueden rotar automáticamente ni ser temporales.
- Solo se aceptan "webhooks" que utilicen HTTPS (SSL).
- La comunicación entre Leah y el "webhook" se hace de servidor a servidor. Es transparente para el usuario final.
- Los "endpoints" deben responder con un código de estado HTTP entre
200
y299
en caso de éxito. Cualquier otro código se considerará fallido. - En caso de que una petición resulte fallida, el sistema de Leah intentará enviar la información hasta dos veces más.
Tipos de confirmaciones
Las confirmaciones que pueden enviarse a un webhook son las siguientes:
- Usuario registrado
- Inducción finalizada
- Examen diagnóstico finalizado
- Examen de expresión oral finalizado
- Nivel General
Puede utilizar un único "endpoint" que gestione todos los tipos de confirmaciones o utilizar distintos "endpoints" para distintas confirmaciones.
Confirmación de usuario registrado
Esta confirmación se envía cuando un usuario se vincula a la organización. Esta vinculación se da de dos maneras: Cuando el usuario redime un código de convenio o cuando redime una clave de licencia. En ambos casos, se envía el evento con la información del usuario que se vinculó.
- Petición
- Cuerpo de la petición
- Tabla de variables
La petición POST
enviada cuando un usuario se registra luce así:
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"]}
El cuerpo de la petición enviada cuando un usuario se registra, luce así:
{
"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"]
}
Las variables enviadas en el cuerpo de la petición cuando un usuario se registra son las siguientes:
Campo | Tipo | Requerido | Descripción |
---|---|---|---|
event | Alfanumérico | Sí | Tipo de evento. En el caso de la confirmación de usuario registrado, siempre tendrá el valor USER_REGISTERED . |
date | Alfanumérico (fecha en el formato simplificado extendido ISO 8601, así: YYYY-MM-DDTHH:mm:ss.sssZ ) | Sí | Fecha en que se registró el usuario. |
user.id | Alfanumérico | Sí | Identificador único del usuario registrado. |
user.personalInformation.email | Alfanumérico (dirección de correo electrónico como se define en la especificación HTML) | Sí | Dirección de correo electrónico del usuario registrado. |
user.personalInformation.familyName | Alfanumérico | Sí | Apellido(s) del usuario registrado. |
user.personalInformation.givenName | Alfanumérico | Sí | Nombre(s) del usuario registrado. |
user.personalInformation.phoneNumber | Alfanumérico (número de teléfono según la especificación E.164) | No | Número de teléfono del usuario registrado. |
user.personalInformation.picture | Alfanumérico (URL) | No | URL de la foto de perfil del usuario. |
user.personalInformation.customFields | Arreglo | No | Listado de campos adicionales del usuario. Por ejemplo, si el usuario proviene de un proveedor de identidad externo (Integración SAML o OIDC), es posible que se hayan definido campos adicionales; si ese es el caso, vendrán listados en este campo. |
user.personalInformation.customFields[].name | Alfanumérico | No | Nombre del campo personalizado. |
user.personalInformation.customFields[].value | Alfanumérico | No | Valor del campo personalizado. |
partner.id | Alfanumérico | Sí | Identificador único del convenio. |
partner.name | Alfanumérico | Sí | Nombre descriptivo del convenio. |
partner.code | Alfanumérico | No | Código de vinculación al convenio. Aplica solo para convenios B2B. |
externalIds | Arreglo (Alfanumérico) | No | En caso de que el usuario se haya registrado utilizando una clave de licencia, el externalId utilizado para crear la licencia vendrá incluido en este listado. Si no incluye ningún valor, significa que el usuario se registró utilizando un código de convenio. |
Confirmación de inducción finalizada
Esta confirmación se envía cuando un usuario completa su información de perfil, autoevaluación y meta de aprendizaje. La información enviada incluye toda esta nueva información.
- Petición
- Cuerpo de la petición
- Tabla de variables
La petición POST
enviada cuando un usuario finaliza la inducción luce así:
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"]}}
El cuerpo de la petición enviada cuando un usuario finaliza la inducción, luce así:
{
"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"]
}
}
Las variables enviadas en el cuerpo de la petición cuando un usuario finaliza la inducción son las siguientes:
Campo | Tipo | Requerido | Descripción |
---|---|---|---|
event | Alfanumérico | Sí | Tipo de evento. En el caso de la confirmación de inducción finalizada, siempre tendrá el valor ONBOARDING_FINISHED . |
date | Alfanumérico (fecha en el formato simplificado extendido ISO 8601, así: YYYY-MM-DDTHH:mm:ss.sssZ ) | Sí | Fecha en que el usuario finalizó la inducción. |
perception.countryCode | Alfanumérico (código de dos letras como se define en la norma ISO 3166-1 alfa-2) | Sí | Código de país al que pertenece el usuario que finalizó la inducción. |
perception.regionName | Alfanumérico | Sí | Nombre de la región a la que pertenece el usuario que finalizó la inducción. |
perception.proficiency.grammarAndVocabulary | Numérico (Entero entre 1 y 5) | Sí | Autoevaluación del usuario en las habilidades de gramática y vocabulario. |
perception.proficiency.readingComprehension | Numérico (Entero entre 1 y 5) | Sí | Autoevaluación del usuario en la habilidad de comprensión lectora. |
perception.proficiency.listeningComprehension | Numérico (Entero entre 1 y 5) | Sí | Autoevaluación del usuario en la habilidad de escucha. |
perception.proficiency.writing | Numérico (Entero entre 1 y 5) | Sí | Autoevaluación del usuario en la habilidad de escritura. |
perception.proficiency.speaking | Numérico (Entero entre 1 y 5) | Sí | Autoevaluación del usuario en la habilidad de conversación. |
perception.goal | Alfanumérico (nivel de inglés según el MCER) | Sí | Meta de aprendizaje autoimpuesta por el usuario. |
perception.timeStudyingEnglish | Alfanumérico | No | Tiempo que el usuario ha estudiado inglés. |
perception.whyIsLearningEnglish | Alfanumérico | No | Razón por la que el usuario está estudiando inglés. |
perception.topicsOfInterest | Arreglo (Alfanumérico) | No | Temas de interés del usuario para su aprendizaje del inglés. |
user.id | Alfanumérico | Sí | Identificador único del usuario que finalizó la inducción. |
user.personalInformation.email | Alfanumérico (dirección de correo electrónico como se define en la especificación HTML) | Sí | Dirección de correo electrónico del usuario que finalizó la inducción. |
user.personalInformation.familyName | Alfanumérico | Sí | Apellido(s) del usuario que finalizó la inducción. |
user.personalInformation.givenName | Alfanumérico | Sí | Nombre(s) del usuario que finalizó la inducción. |
user.personalInformation.phoneNumber | Alfanumérico (número de teléfono según la especificación E.164) | No | Número de teléfono del usuario que finalizó la inducción. |
user.personalInformation.picture | Alfanumérico (URL) | No | URL de la foto de perfil del usuario. |
user.personalInformation.customFields | Arreglo | No | Listado de campos adicionales del usuario. Por ejemplo, si el usuario proviene de un proveedor de identidad externo (Integración SAML o OIDC), es posible que se hayan definido campos adicionales; si ese es el caso, vendrán listados en este campo. |
user.personalInformation.customFields[].name | Alfanumérico | No | Nombre del campo personalizado. |
user.personalInformation.customFields[].value | Alfanumérico | No | Valor del campo personalizado. |
partner.id | Alfanumérico | Sí | Identificador único del convenio. |
partner.name | Alfanumérico | Sí | Nombre descriptivo del convenio. |
partner.code | Alfanumérico | No | Código de vinculación al convenio. Aplica solo para convenios B2B. |
externalIds | Arreglo (Alfanumérico) | No | En caso de que el usuario se haya registrado utilizando una clave de licencia, el externalId utilizado para crear la licencia vendrá incluido en este listado. Si no incluye ningún valor, significa que el usuario se registró utilizando un código de convenio. |
Confirmación de examen diagnóstico finalizado
Esta confirmación se envía cuando un usuario vinculado a la organización finaliza un examen diagnóstico. La información enviada incluye los resultados del usuario en el examen.
- Petición
- Cuerpo de la petición
- Tabla de variables
La petición POST
enviada cuando un usuario finaliza un examen diagnóstico luce así:
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"]}
El cuerpo de la petición enviada cuando un usuario finaliza un examen diagnóstico, luce así:
{
"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"]
}
Las variables enviadas en el cuerpo de la petición cuando un usuario finaliza un examen diagnóstico son las siguientes:
Campo | Tipo | Requerido | Descripción |
---|---|---|---|
event | Alfanumérico | Sí | Tipo de evento. En el caso de la confirmación de examen diagnóstico finalizado, siempre tendrá el valor PLACEMENT_TEST_FINISHED . |
date | Alfanumérico (fecha en el formato simplificado extendido ISO 8601, así: YYYY-MM-DDTHH:mm:ss.sssZ ) | Sí | Fecha en que el usuario finalizó la inducción. |
test.id | Alfanumérico | Sí | Identificador único del examen diagnóstico finalizado. |
test.start | Alfanumérico (fecha en el formato simplificado extendido ISO 8601, así: YYYY-MM-DDTHH:mm:ss.sssZ ) | Sí | Fecha de inicio del examen. |
test.end | Alfanumérico (fecha en el formato simplificado extendido ISO 8601, así: YYYY-MM-DDTHH:mm:ss.sssZ ) | Sí | Fecha de finalización del examen. |
test.questionCount | Numérico (Entero) | Sí | Cantidad de preguntas del examen. |
test.hasProctoring | Booleano | Sí | Si el convenio está haciendo uso del sistema de supervisión, este campo será true . De otra forma, será false . |
test.proctoringWarning | Booleano | No | Si el convenio está haciendo uso del sistema de supervisión y se detecta un posible fraude por parte del usuario que tomó el examen, este campo será true . De otra forma, será false . |
test.result.level | Alfanumérico | Sí | Nivel obtenido por el usuario en el examen. |
test.result.sublevel | Alfanumérico | Sí | Sub-nivel obtenido por el usuario en el examen. |
test.result.score | Numérico (Valor con coma decimal entre 0 y 100) | Sí | Puntuación general obtenida por el usuario en el examen. |
test.result.languageScore | Numérico (Valor con coma decimal entre 0 y 100) | Sí | Puntuación obtenida por el usuario en el examen para la habilidad de uso del lenguaje. |
test.result.readingScore | Numérico (Valor con coma decimal entre 0 y 100) | Sí | Puntuación obtenida por el usuario en el examen para la habilidad de lectura. |
test.result.listeningScore | Numérico (Valor con coma decimal entre 0 y 100) | Sí | Puntuación obtenida por el usuario en el examen para la habilidad de escucha. |
test.result.pdf | Alfanumérico (URL) | No | URL del reporte en formato PDF del resultado obtenido por el usuario en el examen. |
user.id | Alfanumérico | Sí | Identificador único del usuario que finalizó el examen. |
user.personalInformation.email | Alfanumérico (dirección de correo electrónico como se define en la especificación HTML) | Sí | Dirección de correo electrónico del usuario que finalizó el examen. |
user.personalInformation.familyName | Alfanumérico | Sí | Apellido(s) del usuario que finalizó el examen. |
user.personalInformation.givenName | Alfanumérico | Sí | Nombre(s) del usuario que finalizó el examen. |
user.personalInformation.phoneNumber | Alfanumérico (número de teléfono según la especificación E.164) | No | Número de teléfono del usuario que finalizó el examen. |
user.personalInformation.picture | Alfanumérico (URL) | No | URL de la foto de perfil del usuario. |
user.personalInformation.customFields | Arreglo | No | Listado de campos adicionales del usuario. Por ejemplo, si el usuario proviene de un proveedor de identidad externo (Integración SAML o OIDC), es posible que se hayan definido campos adicionales; si ese es el caso, vendrán listados en este campo. |
user.personalInformation.customFields[].name | Alfanumérico | No | Nombre del campo personalizado. |
user.personalInformation.customFields[].value | Alfanumérico | No | Valor del campo personalizado. |
partner.id | Alfanumérico | Sí | Identificador único del convenio. |
partner.name | Alfanumérico | Sí | Nombre descriptivo del convenio. |
partner.code | Alfanumérico | No | Código de vinculación al convenio. Aplica solo para convenios B2B. |
externalIds | Arreglo (Alfanumérico) | No | En caso de que el usuario se haya registrado utilizando una clave de licencia, el externalId utilizado para crear la licencia vendrá incluido en este listado. Si no incluye ningún valor, significa que el usuario se registró utilizando un código de convenio. |
Confirmación de examen de expresión oral finalizado
Esta confirmación se envía cuando un usuario vinculado a la organización finaliza un examen de expresión oral. La información enviada incluye los resultados del usuario.
- Petición
- Cuerpo de la petición
- Tabla de variables
La petición POST
enviada cuando un usuario finaliza un examen de expresión oral luce así:
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"]}
El cuerpo de la petición enviada cuando un usuario finaliza un examen de expresión oral, luce así:
{
"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"]
}
Las variables enviadas en el cuerpo de la petición cuando un usuario finaliza un examen de expresión oral son las siguientes:
Campo | Tipo | Requerido | Descripción |
---|---|---|---|
event | Alfanumérico | Sí | Tipo de evento. En el caso de la confirmación de examen de expresión oral finalizado, siempre tendrá el valor SPEAKING_TEST_FINISHED . |
date | Alfanumérico (fecha en el formato simplificado extendido ISO 8601, así: YYYY-MM-DDTHH:mm:ss.sssZ ) | Sí | Fecha en que el usuario finalizó la inducción. |
test.id | Alfanumérico | Sí | Identificador único del examen de expresión oral finalizado. |
test.start | Alfanumérico (fecha en el formato simplificado extendido ISO 8601, así: YYYY-MM-DDTHH:mm:ss.sssZ ) | Sí | Fecha de inicio del examen. |
test.end | Alfanumérico (fecha en el formato simplificado extendido ISO 8601, así: YYYY-MM-DDTHH:mm:ss.sssZ ) | Sí | Fecha de finalización del examen. |
test.questionCount | Numérico (Entero) | Sí | Cantidad de preguntas del examen. |
test.isValid | Booleano | Sí | Especifica si el examen es válido. Un examen de expresión oral es válido cuando el usuario cumplió con todos los lineamientos (duración, contexto, etc.) y el reconocimiento de voz fue satisfactorio para todas las respuestas. |
test.hasProctoring | Booleano | Sí | Si el convenio está haciendo uso del sistema de supervisión, este campo será true . De otra forma, será false . |
test.proctoringWarning | Booleano | No | Si el convenio está haciendo uso del sistema de supervisión y se detecta un posible fraude por parte del usuario que tomó el examen, este campo será true . De otra forma, será false . |
test.result.level | Alfanumérico | Sí | Nivel obtenido por el usuario en el examen. |
test.result.sublevel | Alfanumérico | Sí | Sub-nivel obtenido por el usuario en el examen. |
test.result.score | Numérico (Valor con coma decimal entre 0 y 100) | Sí | Puntuación general obtenida por el usuario en el examen. |
test.result.vocabularyScore | Numérico (Valor con coma decimal entre 0 y 100) | Sí | Puntuación de vocabulario obtenida por el usuario en el examen. |
test.result.fluencyScore | Numérico (Valor con coma decimal entre 0 y 100) | Sí | Puntuación de fluidez obtenida por el usuario en el examen. |
test.result.pronunciationScore | Numérico (Valor con coma decimal entre 0 y 100) | Sí | Puntuación de pronunciación obtenida por el usuario en el examen. |
test.result.grammarScore | Numérico (Valor con coma decimal entre 0 y 100) | Sí | Puntuación de gramática obtenida por el usuario en el examen. |
test.result.pdf | Alfanumérico (URL) | No | URL del reporte en formato PDF del resultado obtenido por el usuario en el examen. |
user.id | Alfanumérico | Sí | Identificador único del usuario que finalizó el examen. |
user.personalInformation.email | Alfanumérico (dirección de correo electrónico como se define en la especificación HTML) | Sí | Dirección de correo electrónico del usuario que finalizó el examen. |
user.personalInformation.familyName | Alfanumérico | Sí | Apellido(s) del usuario que finalizó el examen. |
user.personalInformation.givenName | Alfanumérico | Sí | Nombre(s) del usuario que finalizó el examen. |
user.personalInformation.phoneNumber | Alfanumérico (número de teléfono según la especificación E.164) | No | Número de teléfono del usuario que finalizó el examen. |
user.personalInformation.picture | Alfanumérico (URL) | No | URL de la foto de perfil del usuario. |
user.personalInformation.customFields | Arreglo | No | Listado de campos adicionales del usuario. Por ejemplo, si el usuario proviene de un proveedor de identidad externo (Integración SAML o OIDC), es posible que se hayan definido campos adicionales; si ese es el caso, vendrán listados en este campo. |
user.personalInformation.customFields[].name | Alfanumérico | No | Nombre del campo personalizado. |
user.personalInformation.customFields[].value | Alfanumérico | No | Valor del campo personalizado. |
partner.id | Alfanumérico | Sí | Identificador único del convenio. |
partner.name | Alfanumérico | Sí | Nombre descriptivo del convenio. |
partner.code | Alfanumérico | No | Código de vinculación al convenio. Aplica solo para convenios B2B. |
externalIds | Arreglo (Alfanumérico) | No | En caso de que el usuario se haya registrado utilizando una clave de licencia, el externalId utilizado para crear la licencia vendrá incluido en este listado. Si no incluye ningún valor, significa que el usuario se registró utilizando un código de convenio. |
Confirmación de Nivel General
Este evento lanza una petición cuando el nivel general de un usuario es calculado.
- Petición
- Cuerpo de la petición
- Table de variables
La petición POST
que se envía cuando se calcula el nivel general, luce así:
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":[]}
El cuerpo de la petición enviado cuando el nivel general es calculado, luce así:
{
"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 | Sí | Event type. When the request is triggered by the overall level calculation, it always has the value OVERALL_LEVEL . |
overall.score | Numérico (Valor con coma decimal entre 0 y 100) | Sí | Puntuación general. |
overall.level | Alfanumérico | Sí | Nivel general. |
overall.sublevel | Alfanumérico | Sí | Sub-nivel general. |
date | Alfanumérico (fecha en el formato simplificado extendido ISO 8601, así: YYYY-MM-DDTHH:mm:ss.sssZ ) | Sí | Fecha cuando el nivel general es calculado. |
placementTest.id | Alfanumérico | Sí | Identificador único del examen diagnóstico finalizado. |
placementTest.start | Alfanumérico (fecha en el formato simplificado extendido ISO 8601, así: YYYY-MM-DDTHH:mm:ss.sssZ ) | Sí | Fecha de inicio del examen. |
placementTest.end | Alfanumérico (fecha en el formato simplificado extendido ISO 8601, así: YYYY-MM-DDTHH:mm:ss.sssZ ) | Sí | Fecha de finalización del examen. |
placementTest.questionCount | Numérico (Entero) | Sí | Cantidad de preguntas del examen. |
placementTest.hasProctoring | Booleano | Sí | Si el convenio está haciendo uso del sistema de supervisión, este campo será true . De otra forma, será false . |
placementTest.proctoringWarning | Booleano | No | Si el convenio está haciendo uso del sistema de supervisión y se detecta un posible fraude por parte del usuario que tomó el examen, este campo será true . De otra forma, será false . |
placementTest.result.level | Alfanumérico | Sí | Nivel obtenido por el usuario en el examen. |
placementTest.result.sublevel | Alfanumérico | Sí | Sub-nivel obtenido por el usuario en el examen. |
placementTest.result.score | Numérico (Valor con coma decimal entre 0 y 100) | Sí | Puntuación general obtenida por el usuario en el examen. |
placementTest.result.languageScore | Numérico (Valor con coma decimal entre 0 y 100) | Sí | Puntuación obtenida por el usuario en el examen para la habilidad de uso del lenguaje. |
placementTest.result.readingScore | Numérico (Valor con coma decimal entre 0 y 100) | Sí | Puntuación obtenida por el usuario en el examen para la habilidad de lectura. |
placementTest.result.listeningScore | Numérico (Valor con coma decimal entre 0 y 100) | Sí | Puntuación obtenida por el usuario en el examen para la habilidad de escucha. |
placementTest.result.pdf | Alfanumérico (URL) | No | URL del reporte en formato PDF del resultado obtenido por el usuario en el examen. |
speakingTest.id | Alfanumérico | Sí | Identificador único del examen de expresión oral finalizado. |
speakingTest.start | Alfanumérico (fecha en el formato simplificado extendido ISO 8601, así: YYYY-MM-DDTHH:mm:ss.sssZ ) | Sí | Fecha de inicio del examen. |
speakingTest.end | Alfanumérico (fecha en el formato simplificado extendido ISO 8601, así: YYYY-MM-DDTHH:mm:ss.sssZ ) | Sí | Fecha de finalización del examen. |
speakingTest.questionCount | Numérico (Entero) | Sí | Cantidad de preguntas del examen. |
speakingTest.isValid | Booleano | Sí | Especifica si el examen es válido. Un examen de expresión oral es válido cuando el usuario cumplió con todos los lineamientos (duración, contexto, etc.) y el reconocimiento de voz fue satisfactorio para todas las respuestas. |
speakingTest.hasProctoring | Booleano | Sí | Si el convenio está haciendo uso del sistema de supervisión, este campo será true . De otra forma, será false . |
speakingTest.proctoringWarning | Booleano | No | Si el convenio está haciendo uso del sistema de supervisión y se detecta un posible fraude por parte del usuario que tomó el examen, este campo será true . De otra forma, será false . |
speakingTest.result.level | Alfanumérico | Sí | Nivel obtenido por el usuario en el examen. |
speakingTest.result.sublevel | Alfanumérico | Sí | Sub-nivel obtenido por el usuario en el examen. |
speakingTest.result.score | Numérico (Valor con coma decimal entre 0 y 100) | Sí | Puntuación general obtenida por el usuario en el examen. |
speakingTest.result.vocabularyScore | Numérico (Valor con coma decimal entre 0 y 100) | Sí | Puntuación de vocabulario obtenida por el usuario en el examen. |
speakingTest.result.fluencyScore | Numérico (Valor con coma decimal entre 0 y 100) | Sí | Puntuación de fluidez obtenida por el usuario en el examen. |
speakingTest.result.pronunciationScore | Numérico (Valor con coma decimal entre 0 y 100) | Sí | Puntuación de pronunciación obtenida por el usuario en el examen. |
speakingTest.result.grammarScore | Numérico (Valor con coma decimal entre 0 y 100) | Sí | Puntuación de gramática obtenida por el usuario en el examen. |
speakingTest.result.pdf | Alfanumérico (URL) | No | URL del reporte en formato PDF del resultado obtenido por el usuario en el examen. |
user.id | Alfanumérico | Sí | Identificador único del usuario que finalizó el examen. |
user.personalInformation.email | Alfanumérico (dirección de correo electrónico como se define en la especificación HTML) | Sí | Dirección de correo electrónico del usuario que finalizó el examen. |
user.personalInformation.familyName | Alfanumérico | Sí | Apellido(s) del usuario que finalizó el examen. |
user.personalInformation.givenName | Alfanumérico | Sí | Nombre(s) del usuario que finalizó el examen. |
user.personalInformation.phoneNumber | Alfanumérico (número de teléfono según la especificación E.164) | No | Número de teléfono del usuario que finalizó el examen. |
user.personalInformation.picture | Alfanumérico (URL) | No | URL de la foto de perfil del usuario. |
user.personalInformation.customFields | Arreglo | No | Listado de campos adicionales del usuario. Por ejemplo, si el usuario proviene de un proveedor de identidad externo (Integración SAML o OIDC), es posible que se hayan definido campos adicionales; si ese es el caso, vendrán listados en este campo. |
user.personalInformation.customFields[].name | Alfanumérico | No | Nombre del campo personalizado. |
user.personalInformation.customFields[].value | Alfanumérico | No | Valor del campo personalizado. |
partner.id | Alfanumérico | Sí | Identificador único del convenio. |
partner.name | Alfanumérico | Sí | Nombre descriptivo del convenio. |
partner.code | Alfanumérico | No | Código de vinculación al convenio. Aplica solo para convenios B2B. |
externalIds | Arreglo (Alfanumérico) | No | En caso de que el usuario se haya registrado utilizando una clave de licencia, el externalId utilizado para crear la licencia vendrá incluido en este listado. Si no incluye ningún valor, significa que el usuario se registró utilizando un código de convenio. |
Ejemplo
A continuación, se encuentran instrucciones para construir un servidor sencillo que sea capaz de funcionar como un "webhook" que puede gestionar los cuatro tipo de confirmaciones que puede enviar Leah (usuario registrado, inducción finalizada, examen diagnóstico finalizado y examen de expresión oral finalizado).
El ejemplo permitirá implementar las siguientes consideraciones:
- Manejar autenticación a través de "Bearer Secret Token" y "Basic HTTP". Si la autenticación es incorrecta, responder con un código de estado HTTP
401 Unauthorized
. - Manejar cualquiera de los distintos tipos de confirmación: Usuario registrado, inducción finalizada, examen diagnóstico finalizado y examen de expresión oral finalizado.
- Validar los tipos de eventos recibidos por cada confirmación, y en caso de que no se detecte un tipo válido responde con un código de estado HTTP
400 Bad request
. Si es un evento correcto responde con un código de estado HTTP200 Success
.
Paso 1: Crear servidor
- Node.js
Guardar el siguiente bloque de código en un archivo con nombre 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;
}
El código de arriba sirve solo a modo de ejemplo y no está pensado para usarse en un entorno real. Contiene malas prácticas y problemas de seguridad como las credenciales incluidas directamente en el código, la comparación de credenciales sin un algoritmo de tiempo constante y no realiza ninguna operación real. Lo único que se realiza es imprimir los datos de la petición en la stdout
.
Paso 2: Ejecutar servidor
El servidor se inicia en el puerto 8080
ejecutando el siguiente comando:
- Node.js
node server.mjs
Paso 3: Simular confirmaciones entrantes
Se pueden hacer peticiones al servidor utilizando curl
. Por ejemplo, para simular el evento de usuario registrado, utilizando autenticación "Basic HTTP", se puede ejecutar el siguiente comando curl:
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"
]
}'
A través del comando curl se envía la información de la confirmación de usuario registrado. Puede probar con cualquiera de las otra confirmaciones utilizando las peticiones de ejemplos discutidas arriba.
Configuración de "webhooks"
Para configurar uno o más "webhooks" debe tener un convenio activo con Leah. Puede ser un convenio que utilice el flujo de "Código de convenio", el flujo de "Claves de licencias" o el flujo de "Usuarios temporales".
- Convenio con claves de licencia
- Convenio con usuarios temporales
- Convenio con código de convenio
Si posee con convenio capaz de generar claves de licencia, Leah le entregó un identificador conocido como "partnerId". Para agregar un "webhook" que reciba confirmaciones de este convenio, debe enviar un correo electrónico al agente o desarrollador que gestiona su convenio con la siguiente información:
- Especificar el "partnerId".
- URL del "endpoint".
- Tipo de autenticación y credenciales del "endpoint".
- Tipo de confirmaciones que deben enviarse a ese "webhook".
Si posee con convenio capaz de generar usuarios temporales ("leads"), Leah le entregó un identificador conocido como "partnerId". Para agregar un "webhook" que reciba confirmaciones de este convenio, debe enviar un correo electrónico al agente o desarrollador que gestiona su convenio con la siguiente información:
- Especificar el "partnerId".
- URL del "endpoint".
- Tipo de autenticación y credenciales del "endpoint".
- Tipo de confirmaciones que deben enviarse a ese "webhook".
Si posee un convenio que vincula sus usuarios a través de un código de convenio, Leah le entregó un código alfanumérico (el mismo utilizado por los usuarios para vincularse a Leah). Para agregar un "webhook" que reciba confirmaciones de este convenio, debe enviar un correo electrónico al agente o desarrollador que gestiona su convenio con la siguiente información:
- Especificar el código de convenio.
- URL del "endpoint".
- Tipo de autenticación y credenciales del "endpoint".
- Tipo de confirmaciones que deben enviarse a ese "webhook"