Nuevo

Monitor de licitaciones

Entrega automática de nuevas licitaciones que coincidan con sus filtros — región, etiquetas de sector, palabras clave, rango de valor. API Pull + resumen por correo electrónico + webhook.

Conceptos

Cuatro términos que aparecerán en todos los endpoints:

  • Tenderuna licitación individual de un portal (NEN, VVZ, E-ZAK, TenderArena, etc.). Identificada por un ID numérico.
  • Filterun conjunto guardado de criterios (región, sector, palabras clave, valor). Cuando una nueva licitación cumple los criterios, se genera una coincidencia.
  • Matchuna nueva licitación que ha encajado en su filtro. Representa el vínculo (licitación × filtro) + marca de tiempo + su estado (destacada / excluida / vista).
  • Taxonomyvocabularios controlados para regiones (NUTS), sectores (etiquetas de industria) y códigos CPV utilizados para clasificar licitaciones.

Inicio rápido

Los filtros pueden configurarse de dos maneras — el resultado es idéntico y un filtro puede editarse a través de cualquiera de las vías en cualquier momento.

curl -H "X-Api-Key: mrw_live_…" \
  "https://veritra.io/api/v2/leads/tenders/search?qText=rekonstrukce%20mostu&limit=5"

Lo anterior es una búsqueda ad hoc. Para la monitorización continua (nuevas licitaciones que coincidan con sus criterios), utilice filtros + webhook/resumen por correo — consulte más adelante.

Recomendación: Utilice siempre industryTags en lugar de categories (CPV) para filtrar. Nuestros industryTags son una combinación de clasificación de licitaciones por LLM y mapeo CPV, lo que los hace robustos frente a códigos CPV incorrectos. Las autoridades contratantes checas asignan con frecuencia códigos CPV demasiado genéricos o no relacionados (habitualmente el genérico '45000000-0' para construcción en lugar de un prefijo específico), por lo que un filtro basado únicamente en CPV dejará fuera una proporción significativa de licitaciones relevantes.

Búsqueda de licitaciones (ad hoc)

Para búsquedas puntuales en todas las licitaciones activas. No utiliza un filtro guardado — los parámetros se incluyen directamente en la cadena de consulta.

GET/api/v2/leads/tenders/search

Parámetros de consulta

ParámetroTipoDescripción
qText*stringTexto de búsqueda (texto completo en título + descripción).
regionsstringCSV de códigos hoja NUTS (CZ010,CZ020).
cpvPrefixesstringCSV de prefijos CPV (45,452).
industryTagsstringCSV de IDs de etiquetas de sector (con_buildings,it_development).
minValuenumberValor estimado mínimo (CZK). Las licitaciones sin valor pasan el filtro.
maxValuenumberValor estimado máximo (CZK). Las licitaciones sin valor pasan el filtro.
deadlineFromstring (YYYY-MM-DD)Fecha límite de presentación >= AAAA-MM-DD.
deadlineTostring (YYYY-MM-DD)Fecha límite de presentación <= AAAA-MM-DD.
sort"newest" | "deadline" | "value"Orden: más recientes (predeterminado), fecha límite (ascendente), valor (descendente).
limitnumberNúmero de resultados, máx. 1000 (predeterminado: 50). Para más de 1k resultados, use paginación nextCursor o /matches/export.
cursorstringCursor de conjunto de claves de la página anterior (pagination.nextCursor).

Ejemplo

curl -H "X-Api-Key: mrw_live_…" \
  "https://veritra.io/api/v2/leads/tenders/search?qText=rekonstrukce&regions=CZ010,CZ020&minValue=500000&limit=10"
GET/api/v2/leads/tenders/:id

Respuesta: { data: { id, title, description, estimatedValue, deadlineAt, contractingAuthority, documents[], starred, excluded } }.

POST/api/v2/leads/tenders/:id/email

El cuerpo puede contener recipientEmail para reenviar a un correo diferente. Por defecto, se usa user.email.

GET/api/v2/leads/documents/preview

Consulta: ?url=:original&kind=docx|xlsx. Hosts permitidos: NEN, E-ZAK, Tender Arena, Gemin, ProfilZadavatele.

GET /api/v2/leads/documents/preview?url=https://nen.nipez.cz/…/Vyzva.docx&kind=docx
→ 302 Location: https://rwx-storage…/Tendero/doc-cache-v8/<hash>.html (signed, TTL 10 min)

Catálogo y autocompletado (público, sin clave)

Para desplegables de interfaz y creación de filtros. Caché en servidor de 1 h. Público, sin autenticación requerida.

GET/api/v2/leads/zadavatele?q=praha
GET/api/v2/leads/countries
GET/api/v2/leads/regions?country=CZ
GET/api/v2/leads/regions/catalog?country=CZ&locale=cs
GET/api/v2/leads/taxonomy/industry
GET/api/v2/leads/taxonomy/cpv
Búsqueda vs. Filtro: La búsqueda es una consulta única que devuelve el estado actual. Un Filtro (ver más adelante) es un criterio guardado — el servidor le notifica continuamente sobre nuevas licitaciones mediante webhook o resumen por correo.

Campos del filtro y valores permitidos

Un filtro está compuesto por los campos que se indican a continuación. Todos son opcionales, excepto `name`. Un campo vacío significa que no hay restricción en esa dimensión.

regions string[]

Regiones NUTS del país indicado (?country=CZ) con etiquetas adaptadas al idioma.

El filtro abarca toda la UE: Los valores de `regions` son códigos NUTS de cualquier país de la UE listado a continuación. El filtro solo devuelve las regiones para las que dispone de una suscripción LEADS activa (ámbito de la suscripción). Si únicamente tiene suscritas CZ + SK, los códigos NUTS alemanes serán ignorados silenciosamente.
Países compatibles (NUTS-0)
CZČeská republika
SKSlovensko
PLPolsko
DENěmecko
ATRakousko
FRFrancie
ESŠpanělsko
ITItálie
NLNizozemsko
BEBelgie
PTPortugalsko
SEŠvédsko
FIFinsko
DKDánsko
NONorsko
IEIrsko
GRŘecko
RORumunsko
BGBulharsko
HUMaďarsko
HRChorvatsko
SISlovinsko
LTLitva
LVLotyšsko
EEEstonsko
LULucembursko
CYKypr
MTMalta
CHŠvýcarsko
ISIsland
MKSeverní Makedonie
GBVelká Británie
JPJaponsko

Para obtener el árbol NUTS completo de un país específico, utilice:

GET /api/v2/leads/regions/catalog?country=CZ&locale=cs
GET /api/v2/leads/regions/catalog?country=DE&locale=de
GET /api/v2/leads/regions/catalog?country=FR&locale=en
Ejemplo: códigos NUTS-3 para CZ (regiones) — expandir ▸
CZ010Hlavní město Praha
CZ020Středočeský kraj
CZ031Jihočeský kraj
CZ032Plzeňský kraj
CZ041Karlovarský kraj
CZ042Ústecký kraj
CZ051Liberecký kraj
CZ052Královéhradecký kraj
CZ053Pardubický kraj
CZ063Kraj Vysočina
CZ064Jihomoravský kraj
CZ071Olomoucký kraj
CZ072Zlínský kraj
CZ080Moravskoslezský kraj

industryTags string[] Recomendado

Clasificación sectorial por múltiples etiquetas. Una licitación puede tener varias etiquetas (p. ej., 'stav_pozemni' + 'stav_remesla' para una renovación). El filtro coincide con una licitación si al menos una de las etiquetas solicitadas está establecida (JSON_OVERLAPS).

Por qué preferir industryTags frente a CPV: Nuestros industryTags combinan la clasificación por LLM del título/descripción de la licitación con el mapeo CPV y patrones regex, lo que permite detectar licitaciones relevantes incluso cuando la autoridad asignó el CPV de forma descuidada. Un filtro CPV puro es exacto, pero depende del rigor de la autoridad contratante, que es escaso en CZ.

48 etiquetas en 13 áreas
🏗️Stavebnictví
con_buildingsPozemní stavby a rekonstrukce budov
con_civilInženýrské stavby (silnice, mosty, voda)
con_tradesStavební řemesla a dílčí práce
con_energy_efficiencyEnergetické úspory a OZE
con_materialsStavební materiál
📐Projektování a dozor
des_documentationProjektová dokumentace a studie
des_supervision_ohsTechnický dozor a BOZP
des_surveyingGeodézie a pozemkové úpravy
💻IT a software
it_developmentVývoj SW a integrace
it_licensingSW licence a předplatné
it_hardwareHW a infrastruktura
it_cybersecurityKybernetická bezpečnost
it_data_aiData, analytika, AI/ML
📡Telekomunikace
telecom_internetTelekomunikace, internet, mobilní
🧑‍💼Profesionální služby
prof_marketingMarketing, PR, reklama
prof_legalPrávní služby
prof_accountingÚčetnictví, dotace, audit
prof_hrHR, nábor
prof_translationPřeklady a tlumočení
prof_insurancePojištění a finanční
🛡️Provoz a údržba
ops_cleaningÚklidové služby
ops_securityOstraha a recepční
ops_maintenanceÚdržba a servis zařízení
ops_wasteOdpadové hospodářství
ops_facilitySpráva nemovitostí
🍽️Stravování a ubytování
cat_cateringStravování, catering
cat_accommodationUbytování a konference
cat_foodPotraviny a nápoje
🚚Doprava a vozidla
trans_transportPřeprava cestujících a nákladu
trans_vehiclesVozidla, díly, leasing
🏥Zdravotnictví
health_pharmaLéčiva a farma
health_devicesZdravotnické přístroje a materiál
health_careZdravotní a sociální péče
📦Zboží a vybavení
goods_furnitureNábytek a vybavení interiérů
goods_clothingOděvy, OOPP, uniformy
goods_electricalElektromateriál
goods_machineryPrůmyslové stroje
Energetika a vodárenství
energy_fuelsPohonné hmoty a paliva
energy_power_heatElektřina a teplo
energy_waterVodárenství
🌳Příroda, lesy, bezpečnost
nat_forestryLesní hospodářství
nat_greeneryÚdržba zeleně a zahradnictví
nat_agricultureZemědělství
defense_safetyHasiči, vojsko, obrana
🔬Věda a vzdělávání
sci_labLaboratorní a měřicí vybavení
sci_researchVýzkum a vývoj
edu_trainingVzdělávání a školení
culture_mediaKultura, knihy, média
¿Falta alguna etiqueta? Si no encuentra una etiqueta para su sector, escriba a michal@veritra.io — la añadiremos.

categories string[] Menos preciso

Prefijos CPV (Vocabulario Común de Contratación Pública) de cualquier longitud, comparados mediante `LIKE 'prefix%'`. Así, '45' captura todas las divisiones de construcción, '4523' solo ingeniería civil y '45316110' solo alumbrado público.

35 divisiones CPV más comunes (2 dígitos)
03Agricultura, silvicultura, pesca
09Combustibles y energía
15Alimentos y bebidas
18Ropa, calzado, EPI
22Impresos, libros
30Máquinas de oficina, equipos informáticos
31Máquinas eléctricas, cables, iluminación
32Radio, TV, telecomunicaciones
33Equipos médicos, productos farmacéuticos
34Vehículos, transporte
35Equipos de seguridad, contra incendios y militares
38Instrumentos de laboratorio y medición
39Mobiliario, equipamiento interior
42Maquinaria industrial
44Materiales y estructuras de construcción
45Obras de construcción
48Software y licencias
50Reparación y mantenimiento
55Restauración, alojamiento
60Transporte (acarreo)
64Servicios postales y de telecomunicaciones
65Servicios públicos (electricidad, agua)
66Servicios financieros y de seguros
70Inmuebles y gestión de instalaciones
71Servicios de arquitectura, diseño e ingeniería
72Servicios TI (desarrollo, integración, soporte)
73Investigación y desarrollo
75Administración pública, defensa
77Servicios agrícolas, forestales y hortícolas
79Servicios empresariales (legal, contabilidad, RRHH, marketing)
80Educación y formación
85Sanidad y servicios sociales
90Residuos, medio ambiente, limpieza
92Cultura, deporte, recreación
98Otros servicios para el público

El catálogo completo (9 454 códigos) está disponible en /docs/leads/cpv — con búsqueda y agrupación por división.

keywords string[]

Coincidencia LIKE en el título y la descripción de la licitación. Sin distinción entre mayúsculas y minúsculas. OR entre elementos. Útil como red de seguridad si industryTags/CPV no capturan todo (p. ej., un tipo de licitación específico que solo aparece en el texto).

"keywords": ["reconstruction", "lighting", "kindergarten"]

minValue / maxValue number | null

Rango del valor estimado de la licitación en CZK. Puede establecer solo minValue, solo maxValue o ambos.

Importante: Las licitaciones sin valor estimado definido (estimatedValue es null o 0) pasan el filtro en ambas direcciones. Muchas autoridades contratantes no publican el valor — excluirlas supondría perder licitaciones.

emailDigest boolean

Si está activado, recibirá un único correo electrónico diario (5:00 UTC) con un resumen de las nuevas coincidencias de este filtro. Valor predeterminado: true. Desactívelo editando el filtro. Los webhooks se configuran por separado, a nivel de cuenta (véase más abajo).

name string · active boolean

`name` — máx. 120 caracteres, obligatorio. `active` — si es false, el filtro se excluye del cron (sin nuevas coincidencias, sin resumen, sin webhook). Úselo para pausar sin eliminar.

Lógica del filtro

Los campos se combinan de la siguiente manera:

MATCH = (authority.NUTS3 ∈ expand(regions))
     AND (minValue ≤ estimatedValue ≤ maxValue  OR  estimatedValue is null or 0)
     AND (industry_or_cpv  OR  keyword_match)

# expand(regions): los códigos NUTS se expanden hasta las hojas NUTS 3
# (CZ → 14 regiones, CZ01 → CZ010, CZ010 → CZ010).

industry_or_cpv:
   if    industryTags set  →  JSON_OVERLAPS(industryTags, tender.industryTags)
   elif  categories set    →  tender.cpvCode LIKE ANY (categories + "%")
   else                    →  false   (no se aplica filtro de sector)

keyword_match:
   if    keywords set      →  tender.title or description LIKE ANY (%kw%)
   else                    →  false

# Si no establece industryTags / categories / keywords,
# se devuelven todas las licitaciones que coincidan con regions y el rango de valor.

industryTags tiene prioridad sobre categories — si establece ambos, solo se utiliza industryTags (categories se ignora). keywords funcionan de forma independiente (se aplica OR con industry_or_cpv).

Endpoints de gestión de filtros

Use la clave de gestión (mrw_live_…) para gestionar filtros. Tiene límites de velocidad independientes y no consume créditos LEADS, por lo que la gestión de filtros no afecta a la recuperación diaria de leads.

GET/api/v2/leads/filters
POST/api/v2/leads/filters
GET/api/v2/leads/filters/:id
PATCH/api/v2/leads/filters/:id
DELETE/api/v2/leads/filters/:id
GET/api/v2/leads/filters/:id/matches/export?format=csv|xlsx|json&view=all|starred|excluded

Parámetros del cuerpo (POST / PUT) — compartidos

ParámetroTipoDescripción
name*stringNombre del filtro (máx. 120 caracteres)
regionsstring[]Códigos NUTS (p. ej., `CZ010`). Combine cualquier nivel. Array vacío = todas las regiones.
industryTagsstring[]Opción recomendada. IDs de etiquetas de nuestra taxonomía (p. ej., 'stav_pozemni', 'it_vyvoj'). Multietiqueta — OR entre elementos.
categoriesstring[]Prefijos CPV de cualquier longitud (p. ej., '45', '4523', '45316110'). Menos preciso que industryTags.
keywordsstring[]Palabras clave — coincidencia LIKE en el título y la descripción de la licitación (OR entre elementos).
minValuenumber | nullValor estimado mínimo (CZK). Las licitaciones sin valor pasan el filtro.
maxValuenumber | nullValor estimado máximo (CZK). Las licitaciones sin valor pasan el filtro.
emailDigestbooleanResumen diario por correo electrónico (predeterminado: true)
activebooleanFiltro activo (predeterminado: true)

Los webhooks ya no se configuran por filtro — consulte la Webhook sección más abajo (endpoints a nivel de cuenta). El webhookUrl campo se rechaza con HTTP 410 por razones de seguridad.

Ejemplos

Los ejemplos siguientes se muestran en inglés para facilitar la lectura. En producción, las palabras clave se comparan (LIKE %kw%) con el título y la descripción de la licitación en el idioma del organismo publicador — actualmente siempre en checo, por lo que debe enviar su filtro con términos en checo (p. ej., "osvětlení", "veřejné osvětlení").

Construcción en Praga por encima de 500 000 CZK

curl -X POST -H "X-Api-Key: mrw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Construcción en Praga 500k+",
    "regions": ["CZ010"],
    "industryTags": ["con_buildings", "con_trades"],
    "minValue": 500000
  }' \
  https://veritra.io/api/v2/leads/filters

Desarrollo TI y licencias de SW (todas las regiones)

curl -X POST -H "X-Api-Key: mrw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Desarrollo TI + licencias de SW",
    "industryTags": ["it_development", "it_licensing", "it_data_ai"],
    "keywords": ["sistema de información", "módulo"]
  }' \
  https://veritra.io/api/v2/leads/filters

Alumbrado público (CZ) — combinación de etiquetas y palabras clave

curl -X POST -H "X-Api-Key: mrw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Alumbrado público (CZ)",
    "industryTags": ["goods_electrical", "con_civil"],
    "keywords": ["alumbrado", "farola", "alumbrado público", "lámparas"],
    "minValue": 200000
  }' \
  https://veritra.io/api/v2/leads/filters

Desactivar un filtro

curl -X PATCH -H "X-Api-Key: mrw_live_…" \
  -H "Content-Type: application/json" \
  -d '{"active": false}' \
  https://veritra.io/api/v2/leads/filters/<id>

Entrega — webhook vs. sondeo

Dos formas de incorporar nuevas licitaciones a su ERP. La mayoría de los integradores combinan ambas.

Webhook (push)

Veritra realiza un POST a su endpoint en ~2 segundos tras la coincidencia. Evento leads.match.created con el payload completo de la licitación.

Ventajas: tiempo real, sin sobrecarga de sondeo, filtrado en servidor.

Inconvenientes: requiere endpoint accesible públicamente (HTTPS), verificación HMAC y gestión de idempotencia.

Sondeo (pull)

Su ERP llama periódicamente a GET /api/v2/leads/matches?since=… (p. ej., cada 5 min). Devuelve las coincidencias desde la marca de tiempo indicada.

Ventajas: no requiere endpoint público, implementación sencilla, tolerante a reinicios.

Inconvenientes: latencia de 5-10 min, restricción de límite de velocidad (60 req/min API de gestión), respuestas vacías desperdiciadas.

Recomendación: webhook principal + sondeo diario (since=yesterday) como red de seguridad en caso de que el webhook agote el presupuesto de reintentos.

Obteniendo coincidencias

GET/api/v2/leads/matches

Parámetros de consulta

ParámetroTipoDescripción
filterIdstringFiltrar por un filtro específico
qTextstringTexto de búsqueda (texto completo en título + descripción).
sincestring (ISO 8601 datetime)Solo coincidencias desde esta fecha (datetime ISO)
deliveredbooleanfalse = solo no entregadas, true = solo entregadas
view"starred" | "excluded"Vista especial: starred (favoritos) | excluded (ocultos).
sort"newest" | "deadline" | "value"Orden: más recientes (predeterminado), fecha límite (ascendente), valor (descendente).
limitnumberNúmero de resultados, máx. 1000 (predeterminado: 50). Para más de 1k resultados, use paginación nextCursor o /matches/export.
cursorstringCursor de conjunto de claves de la página anterior (pagination.nextCursor).

Las coincidencias devueltas se marcan automáticamente como delivered=true. Cada solicitud consume 1 crédito.

Formatos de matchId

matchId tiene dos formatos según el origen:

  • cm… (prefijo cuid) — devuelto desde la tabla LeadMatch precomputada (cron diario). Estable entre llamadas; permite usar mark-as-viewed.
  • live-12345 Prefijo sintético para coincidencias detectadas en tiempo real mediante búsqueda / modo exploración (parámetro qText, view=starred/excluded). No figura en la tabla LeadMatch → mark-as-viewed es una operación sin efecto. tenderId es el sufijo tras el guion.

Ejemplo

curl -H "X-Api-Key: mrw_leads_…" \
  "https://veritra.io/api/v2/leads/matches?delivered=false&limit=10"

Respuesta

{
  "data": [
    {
      "matchId": "cm…",
      "filterId": "cm…",
      "filterName": "Construcción en Praga",
      "matchedAt": "2026-04-03T06:00:00.000Z",
      "viewedAt": null,
      "delivered": true,
      "tender": {
        "id": 12345,
        "title": "Reconstrucción del puente n.º de reg. 123",
        "estimatedValue": 12500000,
        "deadlineAt": "2026-05-15T22:00:00.000Z",
        "publishedAt": "2026-04-01T08:00:00.000Z",
        "firstSeenAt": "2026-04-01T08:30:00.000Z",
        "url": "https://nen.nipez.cz/...",
        "portalType": "NEN",
        "cpvCode": "45000000",
        "tenderType": "OFFERS",
        "contractingAuthority": {
          "ico": "12345678",
          "name": "Město Praha",
          "region": "Praha",
          "district": "Praha 1"
        },
        "documents": [
          { "name": "Výzva.pdf", "url": "https://nen.nipez.cz/…", "fileType": "pdf", "fileSizeBytes": 320000 }
        ],
        "starred": false,
        "excluded": false
      }
    }
  ],
  "pagination": { "nextCursor": "eyJmaXJzdFNlZW5BdC…", "totalCount": 42 }
}

Para la página siguiente, pase pagination.nextCursor como parámetro ?cursor=.

Acciones sobre coincidencias

Destacar (favorito), excluir (ocultar) y ver (marcar como leído) son preferencias por licitación almacenadas en UserTenderPreference.

GET/api/v2/leads/matches/:matchId

Respuesta: misma estructura que el elemento de la lista de coincidencias.

POST/api/v2/leads/matches/:matchId/star
{ "starred": true }
POST/api/v2/leads/matches/:matchId/exclude
{ "excluded": true }
POST/api/v2/leads/matches/:matchId/view

Sin efecto para coincidencias sintéticas live-:id (no hay fila que marcar).

GET/api/v2/leads/preferences
{
  "data": {
    "starred": [12345, 67890],
    "excluded": [54321]
  }
}

Taxonomía

Catálogos de referencia estáticos para valores de filtro. Labels adaptados al idioma.

GET/api/v2/leads/taxonomy/industry?locale=cs
{
  "data": {
    "locale": "cs",
    "areas": [{ "id": "construction", "icon": "🏗️", "label": "Stavebnictví" }, …],
    "tags": [
      { "id": "con_buildings", "area": "construction", "label": "Pozemní stavby", "cpvPrefixes": ["452"] },
      …
    ]
  }
}
GET/api/v2/leads/taxonomy/cpv?locale=cs

Respuesta: estructura de árbol divisiones → grupos → clases → categorías → subcategorías.

Webhook

Los endpoints de webhook (CRUD, rotación de secreto) son a nivel de cuenta — documentados una sola vez en API de cuenta → Webhooks. Esta sección cubre únicamente el payload del evento específico de leads (leads.match.created) y la verificación HMAC.

Entrega de eventos

Ante una nueva coincidencia, enviamos un evento de tipo leads.match.created con una firma HMAC-SHA256 en el encabezado X-Signature-256 encabezado, clave de idempotencia en X-Idempotency-Key y el tipo en X-Event-Type. En caso de fallo, reintentamos con retroceso exponencial durante un máximo de ~33 horas.

Política de reintentos

Si su endpoint devuelve un código no 2xx (o no responde en 10 s), Veritra reintenta con retroceso exponencial: 1 min, 5 min, 30 min, 2 h, 12 h, 24 h. Tras 6 fallos, el webhook se marca como fallido y se señala en el panel. La clave de idempotencia permanece igual en cada reintento — su endpoint DEBE deduplicar (de lo contrario, la misma coincidencia se procesa 6 veces).

Gestionar endpoints mediante API

Puede gestionar los endpoints de webhook sin acceder al panel. Límite de 5 endpoints por cuenta.

GET/api/v2/account/webhooks
POST/api/v2/account/webhooks
¡El secreto del webhook se muestra una sola vez! Cuando POST /webhooks tiene éxito, la respuesta incluye un campo 'secret' — guárdelo inmediatamente en su entorno (p. ej., VERITRA_WEBHOOK_SECRET). No podrá recuperarse más adelante. Si se pierde, utilice /rotate-secret para generar uno nuevo (el anterior queda invalidado de inmediato).
GET/api/v2/account/webhooks/:id
PATCH/api/v2/account/webhooks/:id
DELETE/api/v2/account/webhooks/:id
POST/api/v2/account/webhooks/:id/rotate-secret
POST/api/v2/account/webhooks/:id/test
GET/api/v2/account/webhooks/:id/deliveries
POST/api/v2/account/webhooks/:id/deliveries/:deliveryId/replay

Verificación de firma HMAC

El servidor firma el payload como HMAC-SHA256(secret, raw_body) y lo envía en la cabecera X-Signature-256 con formato sha256=hex. Verifique en tiempo constante; de lo contrario, la integración es vulnerable a ataques de temporización.

// Node.js (Express)
import crypto from "node:crypto";

const WEBHOOK_SECRET = process.env.MRICKWOOD_WEBHOOK_SECRET!;

function verify(rawBody: string, sig: string | undefined): boolean {
  if (!sig) return false;
  const expected = "sha256=" + crypto
    .createHmac("sha256", WEBHOOK_SECRET)
    .update(rawBody)
    .digest("hex");
  // Constant-time comparison (timing-safe)
  const a = Buffer.from(sig);
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

app.post("/webhooks/veritra",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    const raw = req.body.toString("utf8");
    if (!verify(raw, req.header("X-Signature-256"))) {
      return res.status(401).send("Invalid signature");
    }
    const idempKey = req.header("X-Idempotency-Key")!;
    const evt = JSON.parse(raw);
    // Idempotency: store idempKey, skip if already processed
    if (await alreadyProcessed(idempKey)) return res.status(200).send("ok");
    await processEvent(evt);
    await markProcessed(idempKey);
    res.status(200).send("ok"); // Must be 2xx within 10s
  },
);
# Python (Flask)
import hmac, hashlib, os
from flask import Flask, request, abort

WEBHOOK_SECRET = os.environ["MRICKWOOD_WEBHOOK_SECRET"]

def verify(raw: bytes, sig: str | None) -> bool:
    if not sig: return False
    expected = "sha256=" + hmac.new(
        WEBHOOK_SECRET.encode(), raw, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(sig, expected)

@app.post("/webhooks/mrickwood")
def webhook():
    raw = request.get_data()
    if not verify(raw, request.headers.get("X-Signature-256")):
        abort(401)
    # ... idempotency check + process
    return "ok", 200
{
  "id": "evt_…",
  "type": "leads.match.created",
  "createdAt": "2026-04-03T06:00:00.000Z",
  "data": {
    "filterId": "cm…",
    "filterName": "Construcción en Praga",
    "matchId": "cm…",
    "tender": { "id": "12345", "title": "…", "estimatedValue": 12500000 }
  }
}

Resumen por correo electrónico

Con emailDigest: true recibirá un correo electrónico diario con un resumen de las nuevas licitaciones. El resumen se envía por la mañana (5:00 UTC) a la dirección de correo electrónico de su cuenta. Puede desactivarlo editando el filtro.

Límites

ParámetroTipoDescripción
Prueba500 req/mes7 días gratuitos. No se requiere tarjeta ni perfil de facturación. ApiKey.requestsLimit predeterminado.
Plan de pagoilimitadoSe elimina el límite mensual. Solo aplica el límite técnico de 100/h/clave.
Filtros20Número máximo de filtros activos por cuenta.
Endpoints de webhook5Número máximo de URLs de webhook activas por cuenta.

Al superar el límite mensual, la API devuelve 429 con una cabecera Retry-After. El límite se restablece el día 1 de cada mes.