Tikra Docs
Volver al sistema
Tikra DocsVolver al sistema
Documentación de Tikra
API del PortalQuick StartAutenticaciónErroresIdempotency, paginación y rate limitsRecetasWebhooksReferencia OpenAPI
API del Portal

Webhooks

Eventos, formato de payload y verificación HMAC-SHA256 para webhooks de Tikra.

Tikra puede enviar eventos a tu sistema vía HTTP POST cuando algo cambia (pedidos, remitos, pagos). Reduce la necesidad de polling.

Setup

  1. Andá a /portal/dashboard/api/webhooks o usá POST /api/v1/portal/webhooks.
  2. Especificá:
    • URL — debe ser HTTPS, accesible desde Internet, no IPs privadas.
    • Eventos — qué eventos querés recibir (lista abajo).
  3. Vas a recibir un signing_secret UNA SOLA VEZ. Guardalo en un vault.

Eventos disponibles

EventoCuándo
order.createdCliente crea pedido
order.approvedAdmin aprueba pedido
order.rejectedAdmin rechaza pedido
order.in_preparationStatus pasa a en preparación
order.in_transitStatus pasa a en tránsito
order.deliveredStatus pasa a entregado
order.failed_deliveryIntento de entrega fallido
order.cancelledCancelación
remito.createdNuevo remito (ingreso o egreso)
remito.voidedRemito anulado
payment.receivedPago registrado
credit_note.issuedNota de crédito emitida
stock.changedStock de distribución de un producto cambia (cualquier causa)
lot.createdLote nuevo creado para distribución
lot.depletedLote queda en 0 (transición positivo → 0)
lot.expiringLote con expiry en próximos 30 días (cron diario 5am AR)

Eventos de stock y lots (v3.36.0)

A partir de v3.36.0 el API emite 4 events adicionales para el lifecycle de stock y lotes de distribución.

stock.changed

Disparado cuando el stock total de distribución de un producto-cliente cambia (cualquier causa: pedido aprobado, remito ingreso/egreso, ajuste manual, CSV import).

{
  "id": "evt_abc123...",
  "type": "stock.changed",
  "created_at": "2026-05-21T14:30:00Z",
  "api_version": "v1",
  "data": {
    "object": {
      "product_id": "uuid",
      "old_quantity": 100,
      "new_quantity": 87,
      "delta": -13,
      "source": null,
      "updated_at": "2026-05-21T14:30:00Z"
    }
  }
}

Notas: source queda null en v1; futuras versiones lo derivarán del stock_movement más reciente. Para obtener el detalle de qué lotes cambiaron, hacé GET /stock/lots?product_id={id} tras recibir el event.

lot.created

Disparado cuando se crea un lote nuevo en context distribución (remito ingreso, transferencia, ajuste).

{
  "id": "evt_abc124...",
  "type": "lot.created",
  "created_at": "2026-05-21T14:30:00Z",
  "api_version": "v1",
  "data": {
    "object": {
      "lot_id": "uuid",
      "product_id": "uuid",
      "lot_number": "L-2026-001",
      "quantity": 50,
      "expiry_date": "2027-12-31",
      "vintage_year": 2024,
      "source_type": "remito_ingreso",
      "created_at": "2026-05-21T14:30:00Z"
    }
  }
}

lot.depleted

Disparado cuando un lote de distribución transiciona de quantity > 0 a quantity = 0.

{
  "id": "evt_abc125...",
  "type": "lot.depleted",
  "created_at": "2026-05-21T14:30:00Z",
  "api_version": "v1",
  "data": {
    "object": {
      "lot_id": "uuid",
      "product_id": "uuid",
      "lot_number": "L-2026-001",
      "expiry_date": "2027-12-31",
      "vintage_year": 2024,
      "depleted_at": "2026-05-21T14:30:00Z"
    }
  }
}

Notas: Si el lote oscila (qty=0 → qty=5 → qty=0 por ajuste/void), se emite múltiples veces — refleja cada transición real.

lot.expiring

Cron diario (5:00 AM AR / 8:00 AM UTC) emite un event por cada lote de distribución con quantity > 0 y expiry_date en los próximos 30 días.

{
  "id": "evt_abc126...",
  "type": "lot.expiring",
  "created_at": "2026-05-21T08:00:00Z",
  "api_version": "v1",
  "data": {
    "object": {
      "lot_id": "uuid",
      "product_id": "uuid",
      "lot_number": "L-2026-001",
      "quantity": 30,
      "expiry_date": "2026-06-15",
      "vintage_year": 2024,
      "days_until_expiry": 25
    }
  }
}

Notas: Re-emite diariamente sin dedupe — usá lot_id + date (del campo created_at del envelope) para deduplicar si querés actuar 1 vez por lote por día.

Formato del payload

{
  "id": "evt_01HX...",
  "type": "order.approved",
  "created_at": "2026-05-09T15:30:00Z",
  "api_version": "v1",
  "data": {
    "object": { }
  }
}

Verificar la firma (HMAC-SHA256)

Cada request incluye el header Tikra-Signature: t=<unix_ts>,v1=<hmac_hex>. Verificá ANTES de procesar.

Node.js / TypeScript

import crypto from "crypto";

function verifyWebhook(body, signature, secret) {
  const [tPart, v1Part] = signature.split(",");
  const t = parseInt(tPart.split("=")[1], 10);
  const expectedSig = v1Part.split("=")[1];

  // Replay protection: tolerá ±5 min de skew
  if (Math.abs(Date.now() / 1000 - t) > 300) {
    throw new Error("Timestamp fuera de tolerancia (replay attack?)");
  }

  const computed = crypto.createHmac("sha256", secret)
    .update(`${t}.${body}`).digest("hex");

  if (!crypto.timingSafeEqual(
    Buffer.from(computed),
    Buffer.from(expectedSig),
  )) {
    throw new Error("Firma inválida");
  }
}

// Express:
app.post("/webhooks/tikra", express.raw({ type: "application/json" }), (req, res) => {
  const signature = req.headers["tikra-signature"];
  const body = req.body.toString("utf8");
  try {
    verifyWebhook(body, signature, process.env.TIKRA_WEBHOOK_SECRET);
  } catch (err) {
    return res.status(401).send(err.message);
  }
  const event = JSON.parse(body);
  console.log("Recibido:", event.type, event.id);
  res.status(200).send("ok");
});

Python

import hmac, hashlib, time

def verify_webhook(body: bytes, signature: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in signature.split(","))
    t = int(parts["t"])
    if abs(time.time() - t) > 300:
        return False
    expected = hmac.new(
        secret.encode(),
        f"{t}.{body.decode()}".encode(),
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])

Comportamiento de retry

Si tu endpoint devuelve un código fuera de 2xx (o hace timeout >10s), Tikra reintenta.

IntentoTiempo desde el anteriorTotal elapsed
10s0s
230s30s
35m~5.5m
430m~35m
52h~2.5h
66h~8.5h
724h~33h

Después del 7° intento sin éxito, el delivery queda como abandoned. Podés reintentarlo manualmente desde el dashboard.

Auto-pausa: después de 10 fallas consecutivas en un endpoint, Tikra lo pone en estado paused y te manda un email. Activalo de nuevo desde el dashboard cuando esté arreglado.

Idempotencia

El campo id (ej. evt_01HX...) es único por evento. Si lo recibís dos veces (porque tu endpoint timeouteó pero la entrega sí llegó), procesalo una sola vez:

const seen = new Set(); // o tu DB
if (seen.has(event.id)) return res.status(200).send("ya procesado");
seen.add(event.id);
// ... procesá

Test desde el dashboard

En /portal/dashboard/api/webhooks/{id} hay un botón Test ping que dispara un evento mock con tipo webhook.test. El payload tiene la misma forma que un evento real:

{
  "id": "evt_test_<uuid>",
  "type": "webhook.test",
  "created_at": "2026-05-09T15:30:00Z",
  "api_version": "v1",
  "data": {
    "object": {
      "message": "Test ping desde el dashboard de Tikra Portal API",
      "endpoint_id": "<endpoint id>"
    }
  }
}

Tu endpoint debería:

  1. Verificar la firma HMAC (igual que con eventos reales).
  2. Detectar type === "webhook.test" y responder 200 OK sin procesar el payload como evento de negocio.

El dashboard muestra el HTTP status, la duración, y el body de la respuesta — útil para depurar.

Nota: webhook.test no aparece en la lista de eventos subscribables porque no corresponde a ningún evento de negocio. Solo se puede disparar manualmente desde el dashboard.

Recetas

Ejemplos de código listos para usar con la Tikra Portal API.

Referencia OpenAPI

Documentación interactiva de todos los endpoints de la Tikra Portal API.

On this page

SetupEventos disponiblesEventos de stock y lots (v3.36.0)stock.changedlot.createdlot.depletedlot.expiringFormato del payloadVerificar la firma (HMAC-SHA256)Node.js / TypeScriptPythonComportamiento de retryIdempotenciaTest desde el dashboard