OROVA.VN — BIZ AI AGENT

/ Guía para desarrolladores

Recibir publicaciones de Orova Social por webhook.

Cómo crear un endpoint que reciba las publicaciones que envía Orova Social y verificar la firma antes de procesarlas.

Orova Social publica directamente en las plataformas que conecta. Cuando una publicación tiene que llegar a otro sitio — un canal interno, tu propia aplicación, una plataforma que Orova todavía no admite — usas el canal API: Orova envía la publicación a una dirección HTTP tuya, firmada con una clave secreta, y a partir de ahí decides tú. Esta guía es para una persona desarrolladora y describe el contrato completo.

/ Resumen

Cómo funciona

Registras una dirección de recepción (https://) y una clave secreta en el canal API de tu proyecto. Orova envía allí una petición POST con cuerpo JSON y una cabecera X-Orova-Signature: el HMAC-SHA256 de los bytes exactos de ese cuerpo, con tu clave. Tu endpoint verifica la firma, se queda con la publicación y devuelve un estado 2xx.

  • La dirección de recepción debe ser https. Orova no guarda el canal si la dirección no es https:// o no tiene dominio: devuelve el código api_url.
  • Un canal por dirección. Orova identifica el canal por la propia dirección de recepción. Guardar de nuevo la misma dirección sobrescribe el canal existente en vez de duplicarlo.
  • Hay un botón Enviar prueba. Con el canal conectado aparece el botón Send test: Orova lanza un envío orova.test a tu endpoint y te muestra el estado HTTP recibido junto con el tiempo de ida y vuelta en milisegundos.

/ Configuración

Configurarlo en Orova

Abre SocialProyectos → tu proyecto → pestaña Canales y busca el bloque API. Tiene dos campos y un botón.

  • 1

    URL de recepción (webhook). La dirección de tu endpoint, empezando por https://. Ahí llegan todas las peticiones.

  • 2

    Clave secreta. La cadena con la que se firma. Si la dejas vacía, Orova genera una al azar, pero la pantalla no vuelve a mostrarla: genera tú una cadena larga, pégala y guarda tu propia copia.

  • 3

    Pulsa Save channel. El canal aparece en la lista con su dirección de recepción. La clave no se vuelve a mostrar.

  • 4

    Pulsa Send test. Orova envía un paquete de ejemplo orova.test. Si tu endpoint responde 2xx, Orova indica “El webhook devolvió 200 en … ms”. Cualquier otro estado se muestra con su código; si no se puede contactar, se informa de un error de red.

/ Contrato

Contrato de la petición

Cada envío llega como una petición con esta forma:

POST <your receiving URL>

Headers:
  Content-Type: application/json
  X-Orova-Signature: sha256=<hex HMAC-SHA256 of the raw body>
  X-Orova-Event: orova.test
  User-Agent: Orova-Social/1.0

Body (JSON):
  {
    "event": "orova.test",
    "workspace_id": 12,
    "project_id": 34,
    "post": {
      "title": "...",
      "content": "...",
      "media": [],
      "channels": ["api"]
    },
    "sent_at": "2026-08-23T01:25:00Z"
  }

El cuerpo es JSON UTF-8 compacto, en una sola línea. Lee event para distinguir los tipos: el envío de prueba lleva orova.test, y la cabecera X-Orova-Event repite ese mismo valor. Tu endpoint debería ignorar los campos desconocidos en lugar de rechazarlos, para que las ampliaciones futuras no te rompan nada.

CampoTipoDescripción
eventstringTipo de evento. El envío de prueba es "orova.test".
workspace_idintegerID del espacio de trabajo desde el que se envió.
project_idinteger | nullID del proyecto al que pertenece el canal, o null si no tiene ninguno.
postobjectLa publicación en sí.
post.titlestringTítulo de la publicación.
post.contentstringCuerpo de la publicación, como texto.
post.mediaarrayAdjuntos de la publicación; array vacío si es solo texto.
post.channelsarray of stringTipos de canal a los que va dirigida, por ejemplo ["api"].
sent_atstringMomento del envío, ISO 8601 en UTC, terminado en "Z".

/ Verificación

Verificar la firma

La cabecera X-Orova-Signature tiene la forma sha256=<hex>, donde <hex> es el HMAC-SHA256 de los bytes en bruto del cuerpo, con la clave del canal. Recalcúlalo exactamente sobre los bytes que recibiste: si analizas el JSON y lo vuelves a serializar no coincidirá, porque un solo espacio distinto cambia la firma.

Node.js

// Node.js / Express — verify X-Orova-Signature, then answer fast
import express from "express";
import crypto from "node:crypto";

const app = express();
const OROVA_SECRET = process.env.OROVA_SECRET;   // the key you saved in Orova

// Keep the RAW bytes: the signature covers them, not re-serialized JSON.
app.post(
  "/orova/social",
  express.raw({ type: "application/json", limit: "5mb" }),
  (req, res) => {
    const sent = req.get("x-orova-signature") || "";
    const mine =
      "sha256=" +
      crypto.createHmac("sha256", OROVA_SECRET).update(req.body).digest("hex");

    // Constant-time compare — never use ===.
    const a = Buffer.from(sent);
    const b = Buffer.from(mine);
    if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
      return res.status(401).json({ error: "bad signature" });
    }

    const payload = JSON.parse(req.body.toString("utf8"));
    if (payload.event === "orova.test") {
      return res.status(200).json({ ok: true });   // the Send test button
    }

    // Real post: hand the work to a queue and reply straight away.
    queuePost(payload).catch(console.error);
    return res.status(200).json({ ok: true });
  },
);

app.listen(3000);

Python

# Python / Flask — same check, same rules
import hashlib
import hmac
import json
import os

from flask import Flask, request

app = Flask(__name__)
OROVA_SECRET = os.environ["OROVA_SECRET"].encode("utf-8")

@app.post("/orova/social")
def orova_social():
    raw = request.get_data()                       # bytes, exactly as sent
    mine = "sha256=" + hmac.new(OROVA_SECRET, raw, hashlib.sha256).hexdigest()
    sent = request.headers.get("X-Orova-Signature", "")
    if not hmac.compare_digest(mine, sent):        # constant-time compare
        return {"error": "bad signature"}, 401

    payload = json.loads(raw)
    if payload.get("event") == "orova.test":
        return {"ok": True}, 200

    queue_post(payload)                            # do the slow work later
    return {"ok": True}, 200

Compara con una función de tiempo constante (crypto.timingSafeEqual, hmac.compare_digest), nunca con una igualdad normal. Si la firma no coincide, devuelve 401 y para: no proceses ese envío.

/ Respuesta

Cómo responder

Orova solo mira el estado HTTP. Cualquier código 2xx cuenta como éxito; el cuerpo de la respuesta puede ser el que quieras.

  • Responde rápido. El botón Send test espera 10 segundos como máximo. El trabajo lento — descargar medios, llamar a otra API — va a una cola, y devuelves 2xx enseguida.
  • Orova nunca reintenta por su cuenta. Es intencionado: los reintentos automáticos contra una API de publicación son la vía habitual para publicar dos veces. Un envío fallido marca la publicación como failed con el motivo, y la persona usuaria lo reintenta desde Orova.
  • Prepárate para un envío repetido. Al reintentar, llega el mismo paquete otra vez. Haz la deduplicación en tu lado, por ejemplo recordando los pares workspace_id y sent_at ya tratados.

/ Seguridad

Seguridad

  • Verifica siempre la firma. Tu endpoint es una URL pública: cualquiera puede llamarla. La firma es lo único que demuestra que el envío viene realmente de Orova.
  • Mantén la clave fuera del código. Guárdala en una variable de entorno o en un gestor de secretos, nunca escrita en un archivo que subas al repositorio.
  • Cambia la clave volviendo a guardar. Introduce la misma dirección de recepción con la clave nueva y pulsa Save channel: Orova sobrescribe el canal. Actualiza tu endpoint a la vez.
  • Desconectar borra la clave. Al desconectar el canal, Orova elimina la clave guardada y deja de enviar a esa dirección.

/ Para terminar

Ese es todo el contrato

Una dirección https, una clave, una comprobación de firma y un estado 2xx: con eso, las publicaciones de Orova Social llegan a tus sistemas. Lo que pase después lo decides tú: dónde se publican, dónde se guardan y cómo se registran.

← Volver a la biblioteca de guías

/ ¿Necesitas ayuda?

¿Atascado con tu endpoint? Abre la sección de Soporte dentro de tu espacio de trabajo, o escríbenos.