Skip to content

Winglark

Referencia para desarrolladores

API y webhooks de Winglark

Lee eventos, invitados, respuestas y entradas; añade invitados; entérate en cuanto pasa algo. Una API REST con JSON, claves con permisos limitados y webhooks firmados.

La API y los webhooks forman parte de los planes Professional y Agency. Las claves y los webhooks los crea la persona propietaria del espacio de trabajo o un responsable, en Empresa → Integraciones.

Visión general

La dirección base es https://winglark.com/api/v1. Cada petición y cada respuesta es JSON sobre HTTPS. Las fechas van en ISO-8601 y UTC; los identificadores son UUID.

La API es para servidores. No envía cabeceras CORS e ignora las cookies: llámala desde tu servidor, nunca desde un navegador, y deja la clave fuera del código del cliente.

Autenticación

Envía la clave en la cabecera Authorization como token bearer. Una clave se muestra una sola vez, al crearla; Winglark solo guarda un resumen cifrado con clave.

Una clave actúa en nombre del miembro que la creó, limitada a sus permisos. Se rechaza si está revocada o caducada, si ese miembro ha salido del espacio de trabajo o ya no puede gestionar integraciones, o si el plan del espacio ya no incluye la API.

curl https://winglark.com/api/v1/events \
  -H "Authorization: Bearer wlk_live_ab12cd34_…"

Permisos

Cada punto de acceso pide un permiso. Una clave con guests:write también puede leer invitados. Los datos de contacto (correo, teléfono) van enmascarados salvo que la clave se creara con datos de contacto visibles por un miembro que puede verlos.

PermisoSignificado
events:readLeer eventos. Títulos, fechas, lugares y estado.
guests:readLeer invitados. La lista de invitados de cada evento.
guests:writeAñadir y editar invitados. Con las mismas reglas que el panel: se aplican el límite del plan y la comprobación de duplicados.
rsvps:readLeer respuestas. Quién respondió y cuántos vienen. Nunca respuestas sobre alimentación, alergias o accesibilidad.
checkins:readLeer entradas. Quién entró, cuándo y por qué puerta.
webhooks:manageGestionar webhooks. Permite que una herramienta de automatización se suscriba y se dé de baja por sí sola.

Errores

Los errores usan el estado HTTP y un cuerpo JSON con un code estable y una frase para personas. Cada respuesta lleva X-Request-Id; indícalo cuando nos escribas.

400
Un parámetro no es válido.
401
Sin clave, o una clave desconocida, revocada o caducada.
403
A la clave le falta el permiso, quien la creó perdió el acceso, o el plan no incluye la API.
404
No existe ese registro en este espacio de trabajo.
409
La petición choca con el registro: dirección duplicada, límite de invitados del plan, evento cerrado.
422
Faltan campos o no son válidos.
429
Demasiadas peticiones; espera Retry-After.
500
Error nuestro. Reintenta con la misma Idempotency-Key.
HTTP/1.1 409 Conflict
X-Request-Id: req_4f1e…

{ "error": { "code": "guest_limit_reached", "message": "This event has reached the guest limit of the workspace plan." } }

Paginación

Las listas empiezan por lo más reciente y devuelven hasta limit elementos (50 por defecto, 100 como máximo). Si has_more es true, pasa next_cursor como cursor para la página siguiente. Las listas de invitados y respuestas aceptan también updated_since, una fecha ISO.

Límites de peticiones

600 peticiones por minuto por clave y 1.200 por red. Al superarlo, la respuesta es 429 con Retry-After en segundos. Las peticiones repetidas con una clave no válida se rechazan durante diez minutos.

Idempotencia

Envía una cabecera Idempotency-Key al crear. Durante 24 horas, un reintento con la misma clave y el mismo cuerpo devuelve la primera respuesta (marcada con Idempotent-Replayed) en lugar de crear un segundo invitado. La misma clave con otro cuerpo se rechaza con 422.

curl -X POST https://winglark.com/api/v1/events/{event_id}/guests \
  -H "Authorization: Bearer wlk_live_ab12cd34_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: crm-10442-create" \
  -d '{ "first_name": "Ada", "email": "ada@example.com" }'

Puntos de acceso

Todas las listas aceptan limit y cursor.

GET/v1/me

La propia clave: sus permisos y su espacio de trabajo. Úsala para probar una clave.

Permiso: —

{
  "object": "api_key",
  "id": "3b2a1c0d-9e8f-4a7b-b6c5-d4e3f2a1b0c9",
  "name": "CRM sync",
  "prefix": "wlk_live_ab12cd34",
  "scopes": ["events:read", "guests:read", "rsvps:read"],
  "contacts_visible": false,
  "created_at": "2026-11-04T10:00:00.000Z",
  "expires_at": null,
  "organization": { "id": "9d8c7b6a-5f4e-4d3c-a2b1-0f9e8d7c6b5a", "name": "Northwind Events" }
}

GET/v1/events

Los eventos del espacio de trabajo, del más reciente al más antiguo. Filtra con status.

Permiso: events:read · ?status=active&limit=20

{
  "object": "list",
  "data": [
    {
      "id": "6f1c2a7e-4b1d-4c55-9a3e-2f8d7b0c1e42",
      "object": "event",
      "title": "Spring partner summit",
      "summary": "",
      "status": "active",
      "type": "corporate",
      "starts_at": "2026-12-03T09:00:00.000Z",
      "ends_at": "2026-12-03T17:00:00.000Z",
      "timezone": "Europe/Berlin",
      "locale": "en",
      "location_mode": "in_person",
      "venue_name": "Hall 4",
      "venue_address": "Messedamm 22, Berlin",
      "rsvp_deadline": "2026-11-26T23:00:00.000Z",
      "created_at": "2026-10-20T08:12:44.120Z",
      "updated_at": "2026-11-02T15:03:10.004Z"
    }
  ],
  "has_more": true,
  "next_cursor": "MjAyNi0xMS0wNFQxMDoyMTowMy41NTFafGI4ZTFkM2M0LTlmMjAtNGE3Yi04YzYxLTVkMmUwZjlhM2IxNw"
}

GET/v1/events/{event_id}

Un evento.

Permiso: events:read

{
  "id": "6f1c2a7e-4b1d-4c55-9a3e-2f8d7b0c1e42",
  "object": "event",
  "title": "Spring partner summit",
  "summary": "",
  "status": "active",
  "type": "corporate",
  "starts_at": "2026-12-03T09:00:00.000Z",
  "ends_at": "2026-12-03T17:00:00.000Z",
  "timezone": "Europe/Berlin",
  "locale": "en",
  "location_mode": "in_person",
  "venue_name": "Hall 4",
  "venue_address": "Messedamm 22, Berlin",
  "rsvp_deadline": "2026-11-26T23:00:00.000Z",
  "created_at": "2026-10-20T08:12:44.120Z",
  "updated_at": "2026-11-02T15:03:10.004Z"
}

GET/v1/events/{event_id}/guests

Los invitados de un evento. include_archived=true añade los que se quitaron de la lista.

Permiso: guests:read · ?updated_since=2026-11-01T00:00:00Z

{
  "object": "list",
  "data": [
    {
      "id": "b8e1d3c4-9f20-4a7b-8c61-5d2e0f9a3b17",
      "object": "guest",
      "event_id": "6f1c2a7e-4b1d-4c55-9a3e-2f8d7b0c1e42",
      "first_name": "Ada",
      "last_name": "Kaya",
      "display_name": "Ada Kaya",
      "email": "a•••@example.com",
      "phone": "••• ••• 321",
      "company": "Northwind",
      "job_title": "Head of Partnerships",
      "locale": "en",
      "guest_type": "standard",
      "status": "pending",
      "source": "api",
      "tags": ["partner"],
      "external_reference": "crm-10442",
      "expected_party_size": 1,
      "archived_at": null,
      "created_at": "2026-11-04T10:21:03.551Z",
      "updated_at": "2026-11-04T10:21:03.551Z"
    }
  ],
  "has_more": true,
  "next_cursor": "MjAyNi0xMS0wNFQxMDoyMTowMy41NTFafGI4ZTFkM2M0LTlmMjAtNGE3Yi04YzYxLTVkMmUwZjlhM2IxNw"
}

GET/v1/events/{event_id}/guests/{guest_id}

Un invitado.

Permiso: guests:read

{
  "id": "b8e1d3c4-9f20-4a7b-8c61-5d2e0f9a3b17",
  "object": "guest",
  "event_id": "6f1c2a7e-4b1d-4c55-9a3e-2f8d7b0c1e42",
  "first_name": "Ada",
  "last_name": "Kaya",
  "display_name": "Ada Kaya",
  "email": "a•••@example.com",
  "phone": "••• ••• 321",
  "company": "Northwind",
  "job_title": "Head of Partnerships",
  "locale": "en",
  "guest_type": "standard",
  "status": "pending",
  "source": "api",
  "tags": ["partner"],
  "external_reference": "crm-10442",
  "expected_party_size": 1,
  "archived_at": null,
  "created_at": "2026-11-04T10:21:03.551Z",
  "updated_at": "2026-11-04T10:21:03.551Z"
}

POST/v1/events/{event_id}/guests

Añadir un invitado. Solo first_name es obligatorio. Se aplican los límites del plan y la regla de direcciones duplicadas.

Permiso: guests:write

{
  "first_name": "Ada",
  "last_name": "Kaya",
  "email": "ada@example.com",
  "company": "Northwind",
  "tags": ["partner"],
  "external_reference": "crm-10442"
}
{
  "id": "b8e1d3c4-9f20-4a7b-8c61-5d2e0f9a3b17",
  "object": "guest",
  "event_id": "6f1c2a7e-4b1d-4c55-9a3e-2f8d7b0c1e42",
  "first_name": "Ada",
  "last_name": "Kaya",
  "display_name": "Ada Kaya",
  "email": "a•••@example.com",
  "phone": "••• ••• 321",
  "company": "Northwind",
  "job_title": "Head of Partnerships",
  "locale": "en",
  "guest_type": "standard",
  "status": "pending",
  "source": "api",
  "tags": ["partner"],
  "external_reference": "crm-10442",
  "expected_party_size": 1,
  "archived_at": null,
  "created_at": "2026-11-04T10:21:03.551Z",
  "updated_at": "2026-11-04T10:21:03.551Z"
}

PATCH/v1/events/{event_id}/guests/{guest_id}

Cambiar el nombre, los datos de contacto, la empresa, el cargo, el idioma, el tipo o las etiquetas de un invitado.

Permiso: guests:write

{ "job_title": "Chief Partnerships Officer", "tags": ["partner", "vip-desk"] }
{
  "id": "b8e1d3c4-9f20-4a7b-8c61-5d2e0f9a3b17",
  "object": "guest",
  "event_id": "6f1c2a7e-4b1d-4c55-9a3e-2f8d7b0c1e42",
  "first_name": "Ada",
  "last_name": "Kaya",
  "display_name": "Ada Kaya",
  "email": "a•••@example.com",
  "phone": "••• ••• 321",
  "company": "Northwind",
  "job_title": "Head of Partnerships",
  "locale": "en",
  "guest_type": "standard",
  "status": "pending",
  "source": "api",
  "tags": ["partner"],
  "external_reference": "crm-10442",
  "expected_party_size": 1,
  "archived_at": null,
  "created_at": "2026-11-04T10:21:03.551Z",
  "updated_at": "2026-11-04T10:21:03.551Z"
}

GET/v1/events/{event_id}/rsvps

Las respuestas de un evento, las modificadas más recientemente primero. Filtra con response.

Permiso: rsvps:read · ?response=yes

{
  "object": "list",
  "data": [
    {
      "id": "0c9b7a5e-3d1f-4e2a-b6c8-9f0e1d2c3b4a",
      "object": "rsvp",
      "event_id": "6f1c2a7e-4b1d-4c55-9a3e-2f8d7b0c1e42",
      "guest_id": "b8e1d3c4-9f20-4a7b-8c61-5d2e0f9a3b17",
      "response": "yes",
      "party_size": 2,
      "attendance_mode": "in_person",
      "revision": 1,
      "responded_via": "link",
      "responded_at": "2026-11-05T18:40:12.000Z",
      "updated_at": "2026-11-05T18:40:12.000Z"
    }
  ],
  "has_more": true,
  "next_cursor": "MjAyNi0xMS0wNFQxMDoyMTowMy41NTFafGI4ZTFkM2M0LTlmMjAtNGE3Yi04YzYxLTVkMmUwZjlhM2IxNw"
}

GET/v1/events/{event_id}/checkins

Los registros de puerta de un evento, del más reciente al más antiguo.

Permiso: checkins:read

{
  "object": "list",
  "data": [
    {
      "id": "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
      "object": "checkin",
      "event_id": "6f1c2a7e-4b1d-4c55-9a3e-2f8d7b0c1e42",
      "guest_id": "b8e1d3c4-9f20-4a7b-8c61-5d2e0f9a3b17",
      "action": "admitted",
      "result": "ok",
      "admitted": 2,
      "occurred_at": "2026-12-03T08:57:31.000Z",
      "recorded_offline": false
    }
  ],
  "has_more": true,
  "next_cursor": "MjAyNi0xMS0wNFQxMDoyMTowMy41NTFafGI4ZTFkM2M0LTlmMjAtNGE3Yi04YzYxLTVkMmUwZjlhM2IxNw"
}

GET/v1/webhooks

Los puntos de recepción del espacio de trabajo.

Permiso: webhooks:manage

{ "object": "list", "data": [{
    "id": "e2d4c6b8-a0f1-4e3d-9c5b-7a8f6e4d2c1b",
    "object": "webhook_endpoint",
    "url": "https://hooks.example.com/winglark",
    "description": "CRM sync",
    "events": ["rsvp.submitted", "checkin.admitted"],
    "event_ids": [],
    "include_contacts": false,
    "active": true,
    "disabled_reason": null,
    "source": "api",
    "created_at": "2026-11-04T10:30:00.000Z"
  }], "has_more": false, "next_cursor": null }

POST/v1/webhooks

Suscribir una dirección. La respuesta incluye el secreto de firma, una sola vez.

Permiso: webhooks:manage

{
  "url": "https://hooks.example.com/winglark",
  "events": ["rsvp.submitted", "checkin.admitted"],
  "event_ids": [],
  "description": "CRM sync"
}
{
  "id": "e2d4c6b8-a0f1-4e3d-9c5b-7a8f6e4d2c1b",
  "object": "webhook_endpoint",
  "url": "https://hooks.example.com/winglark",
  "description": "CRM sync",
  "events": ["rsvp.submitted", "checkin.admitted"],
  "event_ids": [],
  "include_contacts": false,
  "active": true,
  "disabled_reason": null,
  "source": "api",
  "created_at": "2026-11-04T10:30:00.000Z",
  "secret": "whsec_6Jx…shown once…"
}

DELETE/v1/webhooks/{webhook_id}

Darse de baja.

Permiso: webhooks:manage

204 No Content

Webhooks

Añade un punto de recepción en el panel o con POST /v1/webhooks, elige sus tipos de suceso y, si quieres, limítalo a algunos eventos. Winglark enviará un mensaje JSON por cada suceso.

Responde con cualquier 2xx en menos de 10 segundos. No se siguen redirecciones. El id del mensaje es el mismo en cada reintento y para cada punto de recepción: úsalo para ignorar un mensaje que ya procesaste.

Tipo de sucesoCuándo
guest.createdSe añadió un invitado: a mano, por importación, por la API o en la puerta.
guest.updatedCambiaron los datos de un invitado.
guest.archivedSe quitó a un invitado de la lista.
rsvp.submittedUn invitado respondió o cambió su respuesta.
invitation.sentSe entregó un correo de invitación al proveedor de correo.
invitation.deliveredEl servidor de correo del destinatario aceptó la invitación.
invitation.bouncedNo se pudo entregar la invitación.
checkin.admittedAlguien entró por la puerta o en un quiosco.
ticket.issuedSe emitieron entradas para un pedido pagado.
event.publishedUn evento entró en marcha: los invitados tienen sus enlaces y la puerta importa.
event.cancelledSe canceló un evento.
seatmap.publishedSe publicó un plano de mesas para los invitados.
badge.printedSe imprimieron acreditaciones.

El mensaje

Todos los mensajes tienen el mismo sobre. data contiene el registro en cuestión, con la misma forma que devuelve la API. Los mensajes nunca incluyen enlaces de invitación, códigos de entrada, la nota privada sobre el invitado ni respuestas sobre alimentación, alergias o accesibilidad.

Winglark-Signature
t=1762338012,v1=5f2b…c9e1
Winglark-Event
rsvp.submitted
Winglark-Delivery
7c1e9a42-…
User-Agent
Winglark-Webhooks/1
Content-Type
application/json
{
  "id": "evt_3f9a1c7e5b2d4a6c8e0f1a2b",
  "object": "event",
  "type": "rsvp.submitted",
  "api_version": "v1",
  "created_at": "2026-11-05T18:40:12.481Z",
  "organization_id": "9d8c7b6a-5f4e-4d3c-a2b1-0f9e8d7c6b5a",
  "event_id": "6f1c2a7e-4b1d-4c55-9a3e-2f8d7b0c1e42",
  "data": {
    "rsvp": {
      "id": "0c9b7a5e-3d1f-4e2a-b6c8-9f0e1d2c3b4a",
      "object": "rsvp",
      "event_id": "6f1c2a7e-4b1d-4c55-9a3e-2f8d7b0c1e42",
      "guest_id": "b8e1d3c4-9f20-4a7b-8c61-5d2e0f9a3b17",
      "response": "yes",
      "party_size": 2,
      "attendance_mode": "in_person",
      "revision": 1,
      "responded_via": "link",
      "responded_at": "2026-11-05T18:40:12.000Z",
      "updated_at": "2026-11-05T18:40:12.000Z"
    },
    "guest": { "id": "b8e1d3c4-9f20-4a7b-8c61-5d2e0f9a3b17", "first_name": "Ada", "last_name": "Kaya", "email": "a•••@example.com", "…": "…" },
    "response": "yes",
    "party_size": 2,
    "revision": 1,
    "revised": false,
    "via": "link"
  }
}

Verificar la firma

Cada mensaje lleva una cabecera Winglark-Signature: t es la hora Unix en que se firmó y v1 el HMAC-SHA256 en hexadecimal de t, un punto y el cuerpo sin procesar, usando como clave el secreto de tu punto de recepción.

Calcúlalo sobre los bytes sin procesar antes de analizar el JSON, compara en tiempo constante y rechaza un t que se aleje más de cinco minutos de tu reloj. Tras una renovación hay dos valores v1 durante 24 horas; acepta el mensaje si coincide cualquiera.

Node.js

import { createHmac, timingSafeEqual } from 'node:crypto';
import express from 'express';

const secret = process.env.WINGLARK_WEBHOOK_SECRET; // whsec_…
const app = express();

// The raw body: the signature covers the bytes, not the parsed JSON.
app.post('/winglark', express.raw({ type: 'application/json' }), (req, res) => {
  const header = req.get('Winglark-Signature') ?? '';
  const parts = header.split(',').map((part) => part.split('='));
  const t = Number(parts.find(([key]) => key === 't')?.[1]);
  const signatures = parts.filter(([key]) => key === 'v1').map(([, value]) => value);

  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return res.sendStatus(400);

  const expected = createHmac('sha256', secret).update(`${t}.`).update(req.body).digest();
  const valid = signatures.some((signature) => {
    const given = Buffer.from(signature, 'hex');
    return given.length === expected.length && timingSafeEqual(given, expected);
  });
  if (!valid) return res.sendStatus(400);

  const event = JSON.parse(req.body.toString('utf8'));
  // Deduplicate on event.id: a message can arrive more than once.
  res.sendStatus(204);
});

app.listen(3000);

Python

import hashlib
import hmac
import json
import os
import time

from flask import Flask, abort, request

SECRET = os.environ["WINGLARK_WEBHOOK_SECRET"].encode()  # whsec_…
app = Flask(__name__)


@app.post("/winglark")
def winglark():
    header = request.headers.get("Winglark-Signature", "")
    parts = [part.split("=", 1) for part in header.split(",") if "=" in part]
    t = next((value for key, value in parts if key == "t"), None)
    signatures = [value for key, value in parts if key == "v1"]

    if not t or not t.isdigit() or abs(time.time() - int(t)) > 300:
        abort(400)

    raw = request.get_data()  # the raw bytes, before any JSON parsing
    expected = hmac.new(SECRET, t.encode() + b"." + raw, hashlib.sha256).hexdigest()
    if not any(hmac.compare_digest(expected, signature) for signature in signatures):
        abort(400)

    event = json.loads(raw)
    # Deduplicate on event["id"]: a message can arrive more than once.
    return "", 204

Reintentos

Un envío que no recibe un 2xx se reintenta tras 1 minuto, 5 minutos, 30 minutos, 2 horas, 6 horas, 12 horas y 24 horas, y después se marca como fallido. Los reintentos salen en la primera ocasión tras su vencimiento, como muy tarde en una hora.

Si todos los envíos a un punto de recepción fallan durante tres días, Winglark deja de enviarle y lo indica en el panel. Reanúdalo cuando el receptor esté arreglado; un envío fallido se puede reenviar desde el registro.

  1. 1. 1 min
  2. 2. 5 min
  3. 3. 30 min
  4. 4. 2 h
  5. 5. 6 h
  6. 6. 12 h
  7. 7. 24 h

Versiones

Esta es la versión 1. Dentro de ella solo añadimos: campos nuevos, tipos de suceso nuevos, parámetros opcionales nuevos. Ignora los campos que no conozcas. Cualquier cambio que rompería una integración irá a una versión nueva, anunciada con antelación.

Plataformas de automatización

Las herramientas compatibles con REST hooks se suscriben con POST /v1/webhooks y se dan de baja con DELETE /v1/webhooks/{id}, usando una clave con webhooks:manage. GET /v1/me prueba una clave. Las listas, de lo más reciente a lo más antiguo, sirven también como datos de ejemplo.