Desarrolladores
translateEnglishAbrir una cuenta

Empieza

  • API para revendedores
  • Inicio rápido

Conceptos

  • Autenticación y modos
  • Errores
  • Idempotencia
  • Límites de uso
  • Paginación

Referencia

  • Endpoints
  • Objetos

Guías

  • Ciclo de una reserva
  • Liga de pago y apartado
  • Cancelaciones
  • Webhooks
  • Sandbox

Más

  • Cambios
data_objectOpenAPI 3.1
menu_bookWebhooksexpand_more

Empieza

  • API para revendedores
  • Inicio rápido

Conceptos

  • Autenticación y modos
  • Errores
  • Idempotencia
  • Límites de uso
  • Paginación

Referencia

  • Endpoints
  • Objetos

Guías

  • Ciclo de una reserva
  • Liga de pago y apartado
  • Cancelaciones
  • Webhooks
  • Sandbox

Más

  • Cambios
data_objectOpenAPI 3.1

Guías

Webhooks

Eventos firmados de la API de Muvon: tipos, contenido, reintentos y cómo verificar Muvon-Signature en Node.

En el portal de partners guardas una URL https por modo (prueba y live). Cada una tiene su secreto whsec_…, que se muestra una vez. El botón de probar manda un evento ping.

Tipos de evento

TipoCuándo
booking.createdSe creó una reserva.
booking.paidTu cliente pagó.
booking.assigned … booking.completedCada estado del viaje. Ve Ciclo de una reserva.
booking.no_showEl pasajero no se presentó.
booking.cancelledSe canceló: por ti, por Muvon o por falta de pago.
pingPrueba desde el portal. No trae reserva.

Contenido

Un POST con Content-Type: application/json y el objeto Event, que trae la reserva completa como quedó:

json
{
  "id": "evt_0a9b8c7d6e5f4a3b",
  "object": "event",
  "type": "booking.paid",
  "created": "2026-11-14T18:22:41-05:00",
  "livemode": false,
  "data": {
    "object": {
      "id": "bk_5d2e8a41c07f4b9e",
      "object": "booking",
      "kind": "transfer",
      "code": "TEST-000001",
      "livemode": false,
      "status": "new",
      "payment_status": "paid",
      "payment_url": "https://api.muvon.mx/pay/r/3c9f0b7e6a5d4c2b1a09f8e7d6c5b4a3",
      "hold_expires_at": null,
      "external_ref": "ORDER-5521",
      "service_type": "airport_pickup",
      "pickup_at": "2026-12-20T14:30:00-05:00",
      "pickup": {
        "place_id": "pl_cun",
        "name": "Cancun Airport (CUN)",
        "address": null
      },
      "dropoff": {
        "place_id": "pl_hotel52",
        "name": "Hotel 52 Playa del Carmen",
        "address": null
      },
      "hours": null,
      "vehicle_type_id": "vt_sedan",
      "passengers": {
        "adults": 2,
        "children": 1
      },
      "bags": 3,
      "extras": [
        {
          "code": "BOOSTER_SEAT",
          "quantity": 1,
          "price_cents": 1000
        }
      ],
      "flight_number": "AA1234",
      "room": null,
      "notes": "Viajamos con una carriola",
      "passenger": {
        "first_name": "Ana",
        "last_name": "García",
        "email": "ana@example.com",
        "phone": "+5215512345678",
        "language": "es"
      },
      "price_cents": 9500,
      "currency": "USD",
      "commission_cents": 950,
      "tracking_url": "https://api.muvon.mx/track?t=a1b2c3d4e5f6",
      "cancel_reason": null,
      "created_at": "2026-11-14T18:10:00-05:00",
      "updated_at": "2026-11-14T18:22:41-05:00"
    }
  }
}

Verificar la firma

Cada envío trae Muvon-Signature: t=<unix>,v1=<firma>. La firma es HMAC-SHA256 en hexadecimal, con tu secreto, de t + . + el cuerpo tal como llegó. Calcúlala sobre el cuerpo crudo (antes de JSON.parse), compárala en tiempo constante y rechaza marcas de tiempo de más de 5 minutos.

verify.js
import crypto from "node:crypto";

// Checks the Muvon-Signature header against the raw body (the exact bytes received, before JSON.parse).
// secret is your endpoint's whsec_… for that mode (test and live have different secrets).
export function verifyMuvonSignature(rawBody, header, secret, toleranceSec = 300) {
  const parts = Object.fromEntries(
    String(header ?? "").split(",").map((p) => p.trim().split("=")),
  );
  const t = Number(parts.t);
  if (!t || !parts.v1) return false;
  // Old timestamps are rejected so a captured request can't be replayed later
  if (Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${t}.${rawBody}`)
    .digest("hex");
  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(parts.v1, "hex");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
import express from "express";
import { verifyMuvonSignature } from "./verify.js";

const app = express();

// express.raw keeps the body as bytes: the signature is over the raw body, not re-serialized JSON
app.post("/webhooks/muvon", express.raw({ type: "application/json" }), async (req, res) => {
  const raw = req.body.toString("utf8");
  if (!verifyMuvonSignature(raw, req.get("Muvon-Signature"), process.env.MUVON_WEBHOOK_SECRET)) {
    return res.status(400).send("bad signature");
  }
  const event = JSON.parse(raw);
  // The same event can arrive twice: skip ids you already processed
  if (await alreadyProcessed(event.id)) return res.sendStatus(200);

  switch (event.type) {
    case "booking.paid":
      await markOrderPaid(event.data.object.external_ref);
      break;
    case "booking.cancelled":
      await markOrderCancelled(event.data.object.external_ref, event.data.object.cancel_reason);
      break;
  }
  await rememberProcessed(event.id);
  res.sendStatus(200); // answer within 10 s; do slow work after answering
});

Respuestas y reintentos

Responde con cualquier 2xx en menos de 10 segundos. Otra respuesta, un error de red o más de 10 s cuentan como fallo, y reintentamos después de 1 min, 5 min, 30 min, 2 h, 6 h, 12 h y 24 h (8 intentos en total). Después el envío queda fallido; lo ves en el portal y puedes recuperar el evento con GET /events.

  • Un evento puede llegar más de una vez: ignora los id que ya procesaste.
  • Pueden llegar en otro orden. Si necesitas el estado actual, consulta GET /bookings/{ref}.
  • Responde primero y procesa después, para no pasar de 10 s.
arrow_backAnteriorCancelacionesSiguientearrow_forwardSandbox

En esta página

  • Tipos de evento
  • Contenido
  • Verificar la firma
  • Respuestas y reintentos