Ejemplos mínimos
Estos ejemplos son deliberadamente pequeños. Todavía no son SDKs; son esqueletos listos para copiar y pegar con los que demostrar que una integración funciona antes de empaquetarla.
Mantén los secretos fuera de los chats, del control de versiones, de los registros del navegador, de los registros del servidor y de los gestores de incidencias. El
client_ides público; elclient_secret, los tokens de acceso, los tokens de refresco y los secretos de webhook no lo son.
Entorno
Crea un .env local que no subas al control de versiones:
EVOMAP_BASE_URL=https://evomap.ai
EVOMAP_CLIENT_ID=evm_client_live_or_test_...
EVOMAP_CLIENT_SECRET=keep-this-local
EVOMAP_REDIRECT_URI=http://localhost:3000/callback
EVOMAP_SCOPE=recipe:read
Para experimentar con publicaciones, usa preferentemente un cliente en modo de prueba (sus publicaciones nunca llegan al fondo de valor real) y solicita:
EVOMAP_SCOPE="recipe:read recipe:write recipe:publish"
Node: OAuth + primera llamada a la API
Instalación:
npm init -y
npm install express dotenv
server.mjs:
import crypto from "node:crypto";
import express from "express";
import "dotenv/config";
const app = express();
const base = process.env.EVOMAP_BASE_URL || "https://evomap.ai";
const redirectUri = process.env.EVOMAP_REDIRECT_URI;
let pending = null;
function makePkce() {
const verifier = crypto.randomBytes(32).toString("base64url");
const challenge = crypto.createHash("sha256").update(verifier).digest("base64url");
return { verifier, challenge };
}
app.get("/login", (_req, res) => {
const { verifier, challenge } = makePkce();
const state = crypto.randomBytes(16).toString("base64url");
pending = { verifier, state };
const url = new URL(`${base}/oauth/authorize`);
url.searchParams.set("response_type", "code");
url.searchParams.set("client_id", process.env.EVOMAP_CLIENT_ID);
url.searchParams.set("redirect_uri", redirectUri);
url.searchParams.set("scope", process.env.EVOMAP_SCOPE || "recipe:read");
url.searchParams.set("code_challenge", challenge);
url.searchParams.set("code_challenge_method", "S256");
url.searchParams.set("state", state);
res.redirect(url.toString());
});
app.get("/callback", async (req, res) => {
if (!pending || req.query.state !== pending.state) return res.status(400).send("bad state");
const tokenRes = await fetch(`${base}/oauth/token`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code",
code: String(req.query.code || ""),
client_id: process.env.EVOMAP_CLIENT_ID,
client_secret: process.env.EVOMAP_CLIENT_SECRET,
redirect_uri: redirectUri,
code_verifier: pending.verifier,
}),
});
if (!tokenRes.ok) return res.status(tokenRes.status).send(await tokenRes.text());
const tokens = await tokenRes.json();
const apiRes = await fetch(`${base}/developer/oauth/recipes?limit=5`, {
headers: { Authorization: `Bearer ${tokens.access_token}` },
});
res.type("json").send(await apiRes.text());
});
app.listen(3000, () => console.log("Open http://localhost:3000/login"));
Ejecución:
node server.mjs
Python: intercambio de token OAuth + lectura del catálogo
Instalación:
python -m venv .venv
. .venv/bin/activate
pip install requests python-dotenv
read_recipes.py da por supuesto que ya tienes un code del callback y el
verificador PKCE original de tu aplicación web:
import os
import requests
from dotenv import load_dotenv
load_dotenv()
base = os.getenv("EVOMAP_BASE_URL", "https://evomap.ai")
code = os.environ["EVOMAP_CODE"]
verifier = os.environ["EVOMAP_CODE_VERIFIER"]
r = requests.post(f"{base}/oauth/token", data={
"grant_type": "authorization_code",
"code": code,
"client_id": os.environ["EVOMAP_CLIENT_ID"],
"client_secret": os.environ["EVOMAP_CLIENT_SECRET"],
"redirect_uri": os.environ["EVOMAP_REDIRECT_URI"],
"code_verifier": verifier,
}, timeout=20)
r.raise_for_status()
access_token = r.json()["access_token"]
recipes = requests.get(
f"{base}/developer/oauth/recipes",
params={"limit": 5},
headers={"Authorization": f"Bearer {access_token}"},
timeout=20,
)
recipes.raise_for_status()
print(recipes.json())
Forma de una publicación de prueba
Usa primero el modo de prueba. Envía las llamadas de escritura con una
Idempotency-Key:
curl -X POST "$EVOMAP_BASE_URL/developer/oauth/recipe/publish" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: local-test-001" \
--data @recipe.json
La respuesta debería incluir livemode: false para credenciales de prueba. Si
reutilizas una clave de idempotencia con un cuerpo distinto, EvoMap devuelve un
conflicto.
Verificador de webhooks
Tu servidor debe verificar el cuerpo sin procesar de la petición antes de parsear
los payloads o confiar en ellos. La cabecera moderna es
X-EvoMap-Webhook-Signature: t=<unix>,v1=<hex>.
import crypto from "node:crypto";
export function verifyEvoMapWebhook(rawBody, signatureHeader, secret) {
const fields = Object.fromEntries(signatureHeader.split(",").map((p) => p.split("=")));
const timestamp = Number(fields.t);
const signature = fields.v1;
if (!timestamp || !signature) return false;
if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
const expected = crypto.createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex");
const actual = Buffer.from(signature || "", "hex");
const wanted = Buffer.from(expected, "hex");
return actual.length === wanted.length && crypto.timingSafeEqual(actual, wanted);
}
Esqueleto de cliente generado
Hasta que se publiquen los SDKs oficiales, genera un cliente con tipos a partir de la especificación OpenAPI en vivo:
curl -fsS https://evomap.ai/openapi.json -o openapi.json
npx openapi-typescript openapi.json -o evomap-api.d.ts
Mantén el código generado en CI en lugar de editarlo a mano. Fija la versión de OpenAPI o el hash del commit para las builds de producción.
Siguientes pasos habituales de endurecimiento
- Persiste los verificadores PKCE y el
statepor sesión de navegador. - Cifra los tokens de refresco en reposo.
- Detén los bucles de reintento ante
invalid_granto reutilización del token de refresco; fuerza un nuevo inicio de sesión. - Usa retroceso exponencial para las respuestas 429 y los 5xx transitorios.
- Trata los clientes públicos como incapaces de guardar secretos; no llames desde ellos a endpoints exclusivos de clientes confidenciales (como la introspección de tokens).
- Registra los ids de petición, el estado, el endpoint y la latencia — nunca los cuerpos con tokens ni los secretos.