Formación · API
Para tu equipo técnico
La formación, conectada a tus programas.
Da de alta personas desde tu programa de nóminas, convoca cursos desde tu portal del empleado y recibe un aviso firmado cada vez que alguien termina un curso o recibe su certificado.
Empezar
Autenticación
Crea una clave en el panel de formación, pestaña Integraciones. Cada clave es de tu empresa, se enseña una sola vez y puede ser de solo lectura o de lectura y escritura. Envíala en cada petición:
curl https://galaworklab.com/api/formacion/v1/espacio \ -H "Authorization: Bearer gwk_XXXXXXXX_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
La API es para servidores: no admite llamadas desde el navegador (sin CORS). Guarda la clave como secreto en tu programa, nunca en una página web. Si se filtra, retírala en el panel: deja de funcionar al instante.
Todo va en JSON (UTF-8). Las fechas son AAAA-MM-DD y las marcas de tiempo, ISO 8601 en UTC.
Límites
Cada respuesta lleva X-RateLimit-Limit y X-RateLimit-Remaining. Si te pasas, recibes 429: espera al minuto siguiente.
Paginación
Los listados devuelven { "datos": [...], "siguiente": "…" }. Pide la página siguiente con ?despues= y el valor de siguiente; cuando es null, has llegado al final. Tamaño con ?limite= (100 por defecto).
curl "https://galaworklab.com/api/formacion/v1/personas?limite=200&despues=alu_8f2c…" \ -H "Authorization: Bearer $GALA_CLAVE"
Errores
Los errores devuelven { "error": "mensaje en español" } con su código HTTP.
| Código | Qué significa |
|---|---|
| 400 | Faltan datos o no son válidos. El mensaje dice cuál. |
| 401 | Falta la clave, no es válida o se ha retirado. |
| 402 | No quedan plazas compradas para ese curso. |
| 403 | La clave es de solo lectura y has intentado escribir. |
| 404 | No existe o no es de tu empresa. |
| 429 | Has superado el límite por minuto. |
Recursos
Espacio
Los datos de tu empresa. Útil para comprobar que la clave funciona.
{ "id": "esp_…", "empresa": "Construcciones Norte, S.L.", "cif": "B12345678" }
Personas
La plantilla en activo: id, nombre, documento, correo, puesto, centro y creado.
Da de alta una persona o hasta 300 de golpe con { "personas": [...] }. Solo es obligatorio el nombre y apellidos. Devuelve el enlace personal de cada una para que se lo hagas llegar, y se aplican tus reglas de grupos.
curl -X POST https://galaworklab.com/api/formacion/v1/personas \
-H "Authorization: Bearer $GALA_CLAVE" -H "Content-Type: application/json" \
-d '{ "personas": [
{ "nombre": "Lucía Martín Ruiz", "documento": "12345678Z", "correo": "[email protected]", "puesto": "Encargada", "centro": "Bilbao" }
] }'
# 201 Created
{ "datos": [ { "id": "alu_…", "nombre": "Lucía Martín Ruiz", "enlace": "https://galaworklab.com/empresas/formacion/curso/#i=…" } ] }
Da de baja a la persona. Su historial y sus certificados se conservan.
Cursos
Los cursos que puedes convocar: los del catálogo con plazas vigentes y los propios publicados (propio: true). Cada uno con id, titulo, horas y norma.
Convocatorias
Cada convocatoria con su curso, fechas y número de personas inscritas.
Convoca un curso a personas sueltas, a grupos o a ambos. Con "invitar": true enviamos la invitación por correo a quien lo tenga. Consume plazas: si no hay, devuelve 402.
curl -X POST https://galaworklab.com/api/formacion/v1/convocatorias \
-H "Authorization: Bearer $GALA_CLAVE" -H "Content-Type: application/json" \
-d '{ "curso": "acoso", "inicio": "2026-11-02", "fin": "2026-11-30",
"personas": ["alu_…"], "grupos": ["gru_…"], "nombre": "Acoso · noviembre", "invitar": true }'
# 201 Created
{ "id": "con_…", "inscritas": 24, "plazas_restantes": 76 }
Inscripciones
El progreso de cada persona en cada convocatoria: estado, nota, intentos, inicio y fin. Filtra con ?convocatoria=, ?persona= o ?estado= (pendiente, en-curso, apto, no-apto).
Certificados
Los certificados emitidos, con la dirección pública para verificarlos. Con ?desde=AAAA-MM-DD traes solo los terminados desde esa fecha: lo normal para sincronizar cada noche con tu expediente de personal.
{ "codigo": "GW-7K2M-…", "persona": "alu_…", "nombre": "Lucía Martín Ruiz", "titulo": "Prevención del acoso laboral",
"horas": 2, "nota": 90, "fin": "2026-11-12", "anulado": null,
"verificar": "https://galaworklab.com/empresas/formacion/verificar/?c=GW-7K2M-…" }
Avisos
Webhooks
Da de alta en el panel (pestaña Integraciones) la dirección https de tu programa y elige los eventos. Te enviamos un POST en JSON en el momento en que ocurren. Responde con un código 2xx en menos de 5 segundos; si no, reintentamos al minuto, a los 5 y 30 minutos, a las 2 horas y a las 12 horas.
| Evento | Cuándo |
|---|---|
inscripcion.creada | Al convocar a una o varias personas (un aviso por convocatoria, con todas las inscripciones nuevas). |
curso.terminado | Cuando alguien aprueba o agota los intentos (resultado: apto o no-apto). |
certificado.emitido | Cuando se genera el certificado, con su código y su dirección de verificación. |
Cabeceras: Gala-Evento, Gala-Entrega (identificador único: úsalo para no procesar dos veces el mismo aviso) y Gala-Firma.
{
"id": "evt_…",
"tipo": "certificado.emitido",
"creado": "2026-11-12T09:41:07Z",
"espacio": "esp_…",
"datos": {
"inscripcion": "ins_…", "convocatoria": "con_…", "curso": "acoso", "version": 3,
"persona": { "id": "alu_…", "nombre": "Lucía Martín Ruiz", "correo": "[email protected]" },
"codigo": "GW-7K2M-…", "titulo": "Prevención del acoso laboral", "horas": 2, "nota": 90, "fin": "2026-11-12",
"verificar": "https://galaworklab.com/empresas/formacion/verificar/?c=GW-7K2M-…"
}
}
Comprobar la firma
Cada aviso lleva Gala-Firma: t=1762940467,v1=5f3a…. Calcula el HMAC-SHA256 en hexadecimal de t + "." + cuerpo (el cuerpo tal cual llega, sin volver a formatearlo) con tu secreto whsec_…, compáralo en tiempo constante con v1 y rechaza avisos con t de hace más de 5 minutos.
Node.js
import crypto from 'node:crypto';
export function avisoValido(cuerpo, cabecera, secreto) {
const { t, v1 } = Object.fromEntries(cabecera.split(',').map((p) => p.split('=')));
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const esperada = crypto.createHmac('sha256', secreto).update(t + '.' + cuerpo).digest('hex');
return v1?.length === esperada.length && crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(esperada));
}
Python
import hmac, hashlib, time
def aviso_valido(cuerpo: bytes, cabecera: str, secreto: str) -> bool:
partes = dict(p.split("=", 1) for p in cabecera.split(","))
t, v1 = partes.get("t", "0"), partes.get("v1", "")
if abs(time.time() - int(t)) > 300:
return False
esperada = hmac.new(secreto.encode(), f"{t}.".encode() + cuerpo, hashlib.sha256).hexdigest()
return hmac.compare_digest(v1, esperada)
¿Has perdido el secreto o crees que se ha filtrado? Pulsa Nuevo secreto en el panel: el anterior deja de valer al momento.