/ 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ódigoapi_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.testa 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 Social → Proyectos → 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.
| Campo | Tipo | Descripción |
|---|---|---|
| event | string | Tipo de evento. El envío de prueba es "orova.test". |
| workspace_id | integer | ID del espacio de trabajo desde el que se envió. |
| project_id | integer | null | ID del proyecto al que pertenece el canal, o null si no tiene ninguno. |
| post | object | La publicación en sí. |
| post.title | string | Título de la publicación. |
| post.content | string | Cuerpo de la publicación, como texto. |
| post.media | array | Adjuntos de la publicación; array vacío si es solo texto. |
| post.channels | array of string | Tipos de canal a los que va dirigida, por ejemplo ["api"]. |
| sent_at | string | Momento 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}, 200Compara 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 segundoscomo 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
failedcon 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_idysent_atya 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.
/ ¿Necesitas ayuda?
¿Atascado con tu endpoint? Abre la sección de Soporte dentro de tu espacio de trabajo, o escríbenos.
