1. Autenticación con API Key
Genera tu API Key desde el dashboard, en la sección "API Keys" (ruta /api-keys, con sesión iniciada). La key completa solo se muestra una vez al crearla, así que cópiala y guárdala en un lugar seguro antes de cerrar el modal.
El formato de la key es msk_live_.... Debes enviarla en cada request como header de autorización:
Authorization: Bearer <tu_api_key>
# Alternativa equivalente
X-API-Key: <tu_api_key>401 Unauthorized
Si falta la API Key en el request.
403 Forbidden
Si la key es inválida o fue revocada.
2. Base URL
Usa la URL correspondiente según el entorno donde estés integrando:
Producción
https://backend-api-production-a17f.up.railway.app
Desarrollo local
http://localhost:3000
3. Endpoints de la API v1
Todos los endpoints listados a continuación requieren una API Key válida enviada en el header Authorization o X-API-Key.
/v1/servicesLista los servicios del usuario dueño de la API key.
Ejemplo de respuesta
{
"services": [
{
"id": 123,
"name": "Consulta inicial",
"durationMinutes": 30,
"requiresGoogleMeet": true
},
{
"id": 124,
"name": "Seguimiento",
"durationMinutes": 15,
"requiresGoogleMeet": false
}
]
}/v1/availability Lista la disponibilidad semanal configurada por el usuario. day_of_week va de 0 (domingo) a 6 (sábado); start_time y end_time están en formato HH:mm.
Ejemplo de respuesta
{
"availability": [
{ "day_of_week": 1, "start_time": "09:00", "end_time": "13:00" },
{ "day_of_week": 1, "start_time": "14:00", "end_time": "18:00" },
{ "day_of_week": 3, "start_time": "09:00", "end_time": "13:00" }
]
}/v1/slots?serviceId=123&date=2026-08-01 Devuelve los horarios libres para un servicio en una fecha específica, como un arreglo de strings "HH:mm". Los parámetros serviceId y date son obligatorios.
Ejemplo de respuesta
{
"date": "2026-08-01",
"serviceId": 123,
"slots": ["09:00", "09:30", "10:00", "10:30", "11:00"]
}/v1/bookingsLista todas las reservas del dueño de la API key.
Ejemplo de respuesta
{
"bookings": [
{
"id": 42,
"serviceId": 123,
"serviceName": "Consulta inicial",
"clientName": "Ana Gómez",
"clientEmail": "ana@ejemplo.com",
"startsAt": "2026-08-01T10:00:00",
"endsAt": "2026-08-01T10:30:00",
"status": "confirmed",
"meetingUrl": null
}
]
}/v1/bookingsCrea una reserva nueva para uno de tus servicios.
Body (JSON)
{
"serviceId": 123,
"clientName": "Ana Gómez",
"clientEmail": "ana@ejemplo.com",
"date": "2026-08-01",
"startTime": "10:00"
}201 · Respuesta (reserva creada)
{
"id": 42,
"serviceId": 123,
"clientName": "Ana Gómez",
"clientEmail": "ana@ejemplo.com",
"startsAt": "2026-08-01T10:00:00",
"endsAt": "2026-08-01T10:30:00",
"status": "confirmed",
"meetingUrl": null
}409 · Horario no disponible
{
"message": "El horario seleccionado ya no está disponible."
}409 · Google no conectado
{
"message": "El anfitrión necesita conectar su cuenta de Google para este servicio.",
"code": "GOOGLE_NOT_CONNECTED"
} El código 409 con "code": "GOOGLE_NOT_CONNECTED" ocurre cuando el servicio requiere Google Meet pero la cuenta de Google del anfitrión no está conectada.
Ejemplo con curl
curl -X POST "https://backend-api-production-a17f.up.railway.app/v1/bookings" \
-H "Authorization: Bearer msk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"serviceId": 123,
"clientName": "Ana Gómez",
"clientEmail": "ana@ejemplo.com",
"date": "2026-08-01",
"startTime": "10:00"
}'Ejemplo con fetch (JavaScript)
const response = await fetch("https://backend-api-production-a17f.up.railway.app/v1/bookings", {
method: "POST",
headers: {
"Authorization": "Bearer msk_live_xxxxxxxxxxxxxxxxxxxxxxxx",
"Content-Type": "application/json"
},
body: JSON.stringify({
serviceId: 123,
clientName: "Ana Gómez",
clientEmail: "ana@ejemplo.com",
date: "2026-08-01",
startTime: "10:00"
})
});
const booking = await response.json();
console.log(booking);/v1/bookings/:id/cancelCancela una reserva propia. El body es opcional.
Body (JSON, opcional)
{
"reason": "El cliente reprogramó por otro medio."
}Respuesta
{
"id": 42,
"status": "cancelled",
"cancellationReason": "El cliente reprogramó por otro medio."
}4. Webhooks salientes
Los webhooks se gestionan desde el dashboard, en /integrations, sección "Webhooks". Registra la URL destino (target_url) donde quieres recibir los eventos y recibirás un secret en texto plano, que se muestra completo porque necesitas pegarlo en tu propio sistema para verificar la firma.
Eventos soportados
booking.created— se dispara cuando se crea una nueva reserva.booking.cancelled— se dispara cuando se cancela una reserva.
Entrega del evento
MeetSyGo hace un POST a tu target_url con estos headers:
Content-Type: application/json
X-MeetSyGo-Event: booking.created
X-MeetSyGo-Signature: sha256=<hmac_hex> La firma X-MeetSyGo-Signature es un HMAC-SHA256 calculado sobre el cuerpo JSON crudo (raw body, tal cual se envía, sin reformatear) usando tu secret como clave.
Ejemplo de payload
{
"id": "evt_9f1c2b6a-1234-4a2b-8c3d-abcdef012345",
"event": "booking.created",
"createdAt": "2026-08-01T10:00:00.000Z",
"data": {
"bookingId": 42,
"hostUsername": "jbaez",
"hostEmail": "jbaez@ejemplo.com",
"client": { "name": "Ana Gómez", "email": "ana@ejemplo.com" },
"service": { "id": 5, "name": "Consulta inicial", "durationMinutes": 30 },
"startsAt": "2026-08-01T10:00:00",
"endsAt": "2026-08-01T10:30:00",
"status": "confirmed",
"meetingUrl": null,
"cancellationReason": null
}
}Verificar la firma (Node.js)
Calcula el HMAC sobre el raw body con crypto.createHmac('sha256', secret) y compáralo contra el valor recibido usando crypto.timingSafeEqual en lugar de una comparación directa ===, para evitar timing attacks.
const crypto = require("crypto");
function isValidSignature(rawBody, signatureHeader, secret) {
if (!signatureHeader || !signatureHeader.startsWith("sha256=")) {
return false;
}
const expectedHex = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
const receivedHex = signatureHeader.slice("sha256=".length);
const expectedBuffer = Buffer.from(expectedHex, "utf8");
const receivedBuffer = Buffer.from(receivedHex, "utf8");
if (expectedBuffer.length !== receivedBuffer.length) {
return false;
}
// Comparación en tiempo constante para evitar timing attacks.
return crypto.timingSafeEqual(expectedBuffer, receivedBuffer);
}
// Ejemplo de uso en un endpoint Express que recibe el rawBody:
app.post("/webhooks/meetsygo", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.header("X-MeetSyGo-Signature");
const event = req.header("X-MeetSyGo-Event");
const rawBody = req.body; // Buffer, sin parsear
if (!isValidSignature(rawBody, signature, process.env.MEETSYGO_WEBHOOK_SECRET)) {
return res.status(401).send("Firma inválida");
}
const payload = JSON.parse(rawBody.toString("utf8"));
console.log("Evento recibido:", event, payload);
res.sendStatus(200);
}); Puedes probar tu configuración en cualquier momento con el botón "Enviar prueba" disponible junto a cada webhook en /integrations.