Dominio verificado: hashcodcodespace.dev (las URL de esta guía usan este dominio)
Integra las constancias en tus sistemas
Verifica obras y archivos tokenizados —también los creados con IA— desde tu código: consulta por lotes, guarda la prueba firmada y recibe avisos firmados cuando una constancia se emite o se revoca. Sin claves de API para leer: todo lo público es verificable por cualquiera.
1 · API de verificación por lotes
POST /api/constancia/public/verify-batch · hasta 50 elementos · 10 consultas por minuto e IP (o la cuota de tu clave de API) · CORS abierto, sin cookies. Cada elemento es un número o {number, sha512} con el SHA-512 del archivo (calcúlalo tú: el archivo nunca se envía).
curl -s https://hashcodcodespace.dev/api/constancia/public/verify-batch \
-H 'Content-Type: application/json' \
-d '{"items":["HC-2026-000001",{"number":"HC-2026-000002","sha512":"<sha512 del archivo>"}]}'# Con clave de integrador (cuota propia; pídela al administrador). Nunca la pongas en código del navegador.
curl -s https://hashcodcodespace.dev/api/constancia/public/verify-batch \
-H "Authorization: Bearer $HASHCOD_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"items":["HC-2026-000001"]}'
# Cabecera X-RateLimit-Limit = tu cuota por minuto; 401 si la clave no es válida o está revocada; 429 si la superas.{
"ok": true, "count": 2,
"results": [
{ "number": "HC-2026-000001", "found": true, "status": "vigente", "valid": true,
"checks": [{ "id": "signature", "ok": true }, { "id": "jcs_hash", "ok": true }, "…"],
"ledgerIntact": true, "genTimeUtc": "2026-11-05T02:00:00Z", "fingerprint": "…",
"verifyUrl": "https://hashcodcodespace.dev/verify/HC-2026-000001" },
{ "number": "HC-2026-000002", "found": true, "valid": true, "fileMatch": true, "aiProvenance": { "generator": "…" } }
]
}- valid: firma, huella, compromiso, sello de tiempo y roles correctos, libro intacto y estado vigente.
- fileMatch: el archivo es el registrado o una hoja de su manifiesto Merkle. En constancias privadas siempre es
null: la API nunca revela si un archivo coincide. - Errores por elemento (
invalid_number,invalid_sha512,found: false) no tumban el lote.
Sin programar: Verificar lote en el navegador, con exportación a CSV.
2 · Prueba portable (JSON-LD firmado)
GET /api/constancia/public/{N}.jsonld entrega la credencial W3C tal como se firmó (eddsa-jcs-2022, Ed25519) con la procedencia de IA. Guárdala: se verifica sin esta plataforma con la clave de /.well-known/did.json.
N=HC-2026-000001
curl -s -o $N.jsonld https://hashcodcodespace.dev/api/constancia/public/$N.jsonld
# La huella de la constancia es el SHA-512 de esos bytes exactos:
sha512sum $N.jsonld
curl -s https://hashcodcodespace.dev/api/constancia/public/$N | grep -o '"fingerprint":"[a-f0-9]*"'
# Clave pública del emisor (guárdala junto a la credencial):
curl -s https://hashcodcodespace.dev/.well-known/did.json3 · Webhooks firmados
El administrador registra tu URL (https pública, puerto 443) y te entrega un secreto whsec_… una sola vez. Reintentos a 1 min, 5 min, 30 min, 2 h y 6 h si no respondes 2xx. Los avisos no contienen datos del titular.
POST /tu/webhook
Content-Type: application/json
X-Hashcod-Event: constancia.issued (o constancia.revoked, ping)
X-Hashcod-Delivery: 6f1c… (id único: úsalo para ignorar duplicados)
X-Hashcod-Timestamp: 1762320000 (segundos Unix)
X-Hashcod-Signature: sha256=<hex> (HMAC-SHA256 del secreto sobre "timestamp.cuerpo")
{ "id": "6f1c…", "event": "constancia.issued", "createdAt": "2026-11-05T02:00:00Z",
"data": { "number": "HC-2026-000001", "status": "vigente", "fingerprint": "…", "genTimeUtc": "…",
"visibility": "public", "revokedAt": null, "verifyUrl": "https://hashcodcodespace.dev/verify/HC-2026-000001" } }Verifica siempre la firma con el cuerpo crudo, compara en tiempo constante, rechaza marcas de tiempo de más de 5 minutos y descarta X-Hashcod-Delivery repetidos.
// Node.js + Express. Usa el cuerpo CRUDO: re-serializar el JSON cambia los bytes y la firma no coincide.
const crypto = require('node:crypto');
const express = require('express');
const app = express();
app.post('/hashcod/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const ts = req.get('X-Hashcod-Timestamp') || '';
const sig = req.get('X-Hashcod-Signature') || '';
const expected = 'sha256=' + crypto.createHmac('sha256', process.env.HASHCOD_WEBHOOK_SECRET)
.update(ts + '.').update(req.body).digest('hex');
const valid = sig.length === expected.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
if (!valid || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return res.sendStatus(401);
const event = JSON.parse(req.body); // event.event, event.data.number…
res.sendStatus(204); // responde 2xx rápido; procesa en segundo plano
});
app.listen(3000);# Python + Flask
import hashlib, hmac, os, time
from flask import Flask, abort, request
app = Flask(__name__)
@app.post('/hashcod/webhook')
def hashcod_webhook():
ts = request.headers.get('X-Hashcod-Timestamp', '')
body = request.get_data() # bytes crudos
expected = 'sha256=' + hmac.new(os.environ['HASHCOD_WEBHOOK_SECRET'].encode(),
ts.encode() + b'.' + body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, request.headers.get('X-Hashcod-Signature', '')):
abort(401)
if not ts.isdigit() or abs(time.time() - int(ts)) > 300:
abort(401)
event = request.get_json() # event['event'], event['data']['number']…
return '', 204<?php
// PHP puro
$body = file_get_contents('php://input');
$ts = $_SERVER['HTTP_X_HASHCOD_TIMESTAMP'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $ts . '.' . $body, getenv('HASHCOD_WEBHOOK_SECRET'));
if (!hash_equals($expected, $_SERVER['HTTP_X_HASHCOD_SIGNATURE'] ?? '') || abs(time() - (int)$ts) > 300) {
http_response_code(401);
exit;
}
$event = json_decode($body, true); // $event['event'], $event['data']['number']…
http_response_code(204);4 · SDK para Node.js y Python
Dos paquetes mínimos sin dependencias, servidos desde este dominio: verifican la firma de la credencial con la clave de did.json, que los bytes descargados sean exactamente los firmados, y la firma de los webhooks. No envían nada a la plataforma salvo las dos descargas públicas.
npm install https://hashcodcodespace.dev/sdk/hashcod-constancia-1.0.1.tgz
// Node.js ≥ 18, sin dependencias (dentro de una función async)
const { verifyConstancia, verifyWebhook } = require('hashcod-constancia');
const r = await verifyConstancia('HC-2026-000001', { base: 'https://hashcodcodespace.dev' });
console.log(r.ok, r.fingerprint); // firma eddsa-jcs-2022 + bytes canónicos
// En tu receptor de webhooks (cuerpo crudo):
verifyWebhook({ secret: process.env.HASHCOD_WEBHOOK_SECRET, body: req.body,
timestamp: req.get('X-Hashcod-Timestamp'), signature: req.get('X-Hashcod-Signature') });pip install https://hashcodcodespace.dev/sdk/hashcod_constancia-1.0.1-py3-none-any.whl
# Python ≥ 3.8, solo biblioteca estándar (Ed25519 incluido)
import os
from hashcod_constancia import verify_constancia, verify_webhook
r = verify_constancia('HC-2026-000001', base='https://hashcodcodespace.dev')
print(r['ok'], r['fingerprint'])
ok = verify_webhook(os.environ['HASHCOD_WEBHOOK_SECRET'], request.get_data(),
request.headers.get('X-Hashcod-Timestamp'), request.headers.get('X-Hashcod-Signature'))
# Terminal: hashcod-constancia HC-2026-000001 (código de salida 0 si es válida)Descarga directa: npm (.tgz) · Python (.whl) · módulo de un archivo (.js).
5 · hcod verify
# Todas las comprobaciones (firma, huella, compromiso, sello de tiempo, roles y ancla externa):
php bin/hcod verify HC-2026-000001 --base https://hashcodcodespace.dev
# Sin depender de la red para el paquete, con el archivo y el prompt:
php bin/hcod verify HC-2026-000001 --cod HC-2026-000001.cod --asset contrato.pdf --prompt prompt.txt
# Código de salida 0 solo si todo pasa: úsalo en scripts y pipelines de CI.