Saltar al contenido principal

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 formato JSON.
  • 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 y 299 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ó.

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"]}

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.

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"]}}

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.

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"]}

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.

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"]}

Confirmación de Nivel General

Este evento lanza una petición cuando el nivel general de un usuario es calculado.

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":[]}

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 HTTP 200 Success.

Paso 1: Crear servidor

Guardar el siguiente bloque de código en un archivo con nombre server.mjs:

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;
}
info

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 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"
]
}'
tip

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".

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:

  1. Especificar el "partnerId".
  2. URL del "endpoint".
  3. Tipo de autenticación y credenciales del "endpoint".
  4. Tipo de confirmaciones que deben enviarse a ese "webhook".