Volkern · Developer Hub
Partner API v1 & Webhook Tester
Integradores externos (ERPs, POS, e-commerce) encuentran aquí el spec OpenAPI navegable y un playground firmado con HMAC-SHA256 para validar que tu endpoint recibe eventos Volkern correctamente.
Genera tu API Key
En el dashboard del tenant (/dashboard/developers) crea una API key con scope invoices:write.
Configura tu webhook
Asocia un webhookUrl público y suscríbete a invoice.accepted, invoice.rejected. Guarda el webhookSecret (se muestra una sola vez).
Verifica HMAC
Tu endpoint recalcula sha256(secret, `$${ts}.$${payload}`) y la compara en tiempo constante contra X-Volkern-Signature.
Webhook Tester
Firma payloads con HMAC-SHA256 (Stripe-compat) y los envía a tu URL. La firma usa X-Volkern-Signature: sha256=<hex> sobre $${timestamp}.$${payload}.
Tu endpoint público. Si es localhost, usa el botón "Vía Volkern (proxy)" o el cURL de abajo.
Lo obtuviste al crear la API Key en /dashboard/developers. La firma HMAC nunca se envía a un servidor (salvo el proxy opcional).
AEAT aceptó la factura y emitió CSV firmado.
La petición directa requiere que tu endpoint acepte CORS o que lo pruebes desde terminal (cURL abajo). El proxy server-side salta CORS pero pasa momentáneamente tu secret por Volkern: no lo uses en producción con secrets reales.
Request preview
cURL
curl -X POST 'https://your-app.example.com/webhook' \
-H 'Content-Type: application/json' \
-H 'X-Volkern-Event: invoice.accepted' \
-H 'X-Volkern-Timestamp: <TIMESTAMP>' \
-H 'X-Volkern-Signature: <SIGNATURE>' \
--data-raw '{
"id": "evt_demo_jfp6zbd2",
"type": "invoice.accepted",
"createdAt": 1792320591,
"tenantId": "cm_demo_tenant",
"data": {
"invoiceId": "cm_demo_invoice_abc123",
"numero": "F-00425",
"amount": 1210,
"currency": "EUR",
"fingerprint": "a1b2c3d4e5f691f50d79eea2fd8dd17083461a9c7caa49e2b1fc2fb189964861",
"aeat_csv_url": "https://aeat.volkern.app/csv/cm_demo_invoice_abc123.csv",
"acceptedAt": "2026-10-18T10:49:51.656Z"
}
}'Reemplaza <TIMESTAMP> y <SIGNATURE> con los valores actuales mostrados arriba, o ejecuta el comando y mira los headers del request recibido en tu servidor.
Response
Envía una request para ver aquí la respuesta de tu endpoint.
Verifica la firma en tu servidor
Compara la firma entrante con un HMAC-SHA256 calculado sobre $${X-Volkern-Timestamp}.$${rawBody} con crypto.timingSafeEqual.
Node.js (Express)
import crypto from 'node:crypto'
app.post('/webhook', (req, res) => {
const ts = req.header('X-Volkern-Timestamp')
const sigHeader = req.header('X-Volkern-Signature') || ''
const match = sigHeader.match(/^sha256=([a-f0-9]+)$/)
if (!ts || !match) return res.status(400).send('Invalid signature header')
const expected = crypto
.createHmac('sha256', process.env.VOLKERN_WEBHOOK_SECRET)
.update(`${ts}.${req.rawBody}`)
.digest('hex')
if (
expected.length !== match[1].length ||
!crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(match[1], 'hex'),
)
) {
return res.status(401).send('Bad signature')
}
// OK → procesa req.body
return res.status(200).send({ received: true })
})Python (Flask)
import hmac, hashlib
from flask import request, abort
@app.post('/webhook')
def webhook():
ts = request.headers.get('X-Volkern-Timestamp')
sig_header = request.headers.get('X-Volkern-Signature', '')
if not ts or not sig_header.startswith('sha256='):
abort(400)
expected = hmac.new(
WEBHOOK_SECRET.encode(),
f'{ts}.{request.get_data()}'.encode(),
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(expected, sig_header.removeprefix('sha256=')):
abort(401)
# OK → procesa request.json
return {'received': True}, 200