Regulatory data API · REST · OpenAPI 3.1 · HMAC webhooks · Signed SQL patches

Regulatory data API: integrate live EU cosmetic data into your product

Stop scraping ec.europa.eu and stop maintaining your own diff engine. BD-API ships the EU CosIng database as signed incremental SQL patches, and multi-source regulatory events (EC Publications, SCCS, Safety Gate, EUR-Lex) as HMAC-signed webhooks. No SDK lock-in, no proprietary runtime.

# Check current CosIng version
curl -X POST https://api.bdapi.app/api/versions/check \
  -H "Authorization: Bearer $LICENSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"current_version": "1.0.0"}'

# If newer version available, download the incremental patch
curl https://api.bdapi.app/api/versions/1.1.0/patch \
  -H "Authorization: Bearer $LICENSE_KEY" \
  -o patch.sql

# Verify SHA-256 integrity (hash ships in response headers)
curl -sI https://api.bdapi.app/api/versions/1.1.0/patch \
  -H "Authorization: Bearer $LICENSE_KEY" \
  | grep -i x-hash-sha256

# Apply inside a single PostgreSQL transaction, then confirm
psql -1 -f patch.sql && curl -X POST .../api/versions/confirm \
  -H "Authorization: Bearer $LICENSE_KEY" \
  -d '{"version":"1.1.0","success":true,"error":null}'

Full pack on request: OpenAPI 3.1 spec, code samples in curl/Node/Python/Go/PHP, webhook contracts for every channel, operational runbooks.

What you get

Four artifacts. No SDK lock-in.

BD-API ships four well-defined deliverables. Everything is HTTP, JSON or SQL — no proprietary client, no opaque binary protocol. Anything that speaks REST and PostgreSQL can integrate.

01CosIng as SQL patches

Incremental, signed, applied in one transaction

Every CosIng release ships as an idempotent patch.sql plus a changelog.json and meta.json. SHA-256 integrity hash sent in response headers (X-Hash-SHA256); apply the patch inside a PostgreSQL transaction and roll back on mismatch.

Patch bundle structure

bdapi-patch-1.2.0.zip
├── patch.sql           # idempotent SQL, runs in a single PostgreSQL transaction
├── changelog.json      # human + machine-readable diff summary
└── meta.json           # version, generated_at, sha256, row counts
02Regulatory events as webhooks

HMAC-SHA256, one unified header namespace, granular per source

EC Publications (Official Journal EU) plus SCCS opinions, Safety Gate alerts and EUR-Lex acts all dispatch as HMAC-signed webhooks over the same X-BDAPI-* headers. You route on the X-BDAPI-Event value — one receiver handles every family. Three retries with 1s / 2s / 4s exponential backoff, idempotency keys on every event. Subscribe per-source.

Inbound webhook request — unified headers

POST https://your-app.example/webhooks/bdapi
# One unified X-BDAPI-* namespace — route on X-BDAPI-Event.

# EC Publications event:
#   X-BDAPI-Event: ec.publication.detected
#   X-BDAPI-Timestamp: 1748016600  (unix seconds)
#   X-BDAPI-Signature: sha256=9f2c…b71d

# Regulatory Watch event (SCCS / Safety Gate / EUR-Lex):
#   X-BDAPI-Event: watch.safety_gate.alert.detected
#   X-BDAPI-Timestamp: 1748016600  (unix seconds)
#   X-BDAPI-Signature: sha256=9f2c…b71d
#   X-BDAPI-Delivery: 01HQZK9YBV…  (UUID per delivery, Watch only)

{ "event": "watch.safety_gate.alert.detected", "tier": "premium", … }
03REST API

Versioned, JSON, OpenAPI 3.1 spec

The full surface is described by an OpenAPI 3.1 YAML document delivered as part of the integration pack. Generate a typed client with openapi-generator, oapi-codegen, openapi-typescript or any other tool that consumes OpenAPI — or stay on raw HTTP. Both paths are first-class.

OpenAPI spec excerpt

openapi: 3.1.0
info:
  title: BD-API — Contrato público para integradores
  version: 1.0.0

# Shipped as part of the integration pack on request — drop into:
#   openapi-generator generate -i 15-OPENAPI.yaml -g typescript-axios
#   oapi-codegen --package bdapi 15-OPENAPI.yaml > bdapi.gen.go
#   prism mock 15-OPENAPI.yaml   # mock server for integration tests
04License-key auth

Bearer token per installation, rotation on request

One license key per installation, sent as Authorization: Bearer $LICENSE_KEY. Keys are installation-scoped — queries filter by client_id automatically. Rotation is requested via support; the planned policy is a 24-hour overlap window (Coming Q3 2026) so you can roll deployments without a hard cutover.

Authenticated request

curl https://api.bdapi.app/api/versions/check \
  -H "Authorization: Bearer $LICENSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"current_version":"1.0.0"}'

# Key rotation: requested via support — old + new accepted
# for a 24h overlap window (Coming Q3 2026).

Sample changelog.json

{
  "from_version": "1.1.4",
  "to_version": "1.2.0",
  "generated_at": "2026-05-15T08:30:00Z",
  "sha256": "9f2c…b71d",
  "changes": {
    "added":    [{ "inci_name": "…", "annex": "VI", "cas": "…" }],
    "modified": [{ "inci_name": "…", "field": "restriction" }],
    "removed":  []
  }
}

Architecture overview

Where BD-API fits in your stack

BD-API is a single upstream component between official EU sources and your application. Two channels: a REST API you poll on your schedule, and outbound HMAC-signed webhooks for time-sensitive events. Nothing else reaches into your database.

┌─────────────────────┐                          ┌──────────────────────┐
│  EU sources         │                          │   Your product       │
│  CosIng · SCCS      │                          │   (cosmetic SW)      │
│  Safety Gate        │      pull  (REST)        │                      │
│  EUR-Lex            │ ◄─────────────────────── │   • PostgreSQL       │
└──────────┬──────────┘                          │   • Webhook endpoint │
           │                                     │   • Your domain UI   │
           │ poll (CRON, advisory-locked)        │                      │
           ▼                                     │                      │
┌─────────────────────┐      push (HMAC)         │                      │
│      BD-API         │ ───────────────────────► │                      │
│   (this service)    │                          │                      │
└─────────────────────┘                          └──────────────────────┘

Integration topology — pull on the REST side, push on the webhook side.

Pull

GET /api/versions/check

Your application polls /api/versions/check on its own schedule (recommended: daily). The response is the current CosIng version plus the SHA-256 of the patch that takes you from your last_applied_version to current. No data is downloaded until you ask for it.

Push

POST to your webhook URL

BD-API sends HMAC-SHA256 signed webhooks when a regulatory event is detected. EC Publications (DG SANTE) and Regulatory Watch (SCCS / Safety Gate / EUR-Lex / IFRA / ECHA) share the same X-BDAPI-* header namespace — X-BDAPI-Event, X-BDAPI-Timestamp, X-BDAPI-Signature — and are told apart by the X-BDAPI-Event value (e.g. `ec.publication.detected` vs `watch.sccs.item.detected`). Three retries, 1s / 2s / 4s exponential backoff. Watch dispatches additionally carry X-BDAPI-Delivery (UUID) for idempotent processing on your side.

Apply

BEGIN; \i patch.sql; COMMIT;

Patches are idempotent SQL designed to run inside a single PostgreSQL transaction. Apply on a replica first if you want — the changelog.json declares row-level impact ahead of time so you can plan downtime if any (most patches are non-blocking).

Verify

SHA-256 + HMAC on every step

Every patch carries a SHA-256 in the X-Hash-SHA256 response header; every webhook carries an HMAC-SHA256 signature over `timestamp + "." + body`. Mismatch on either side means rollback (patch) or 4xx return (webhook). Failures are observable from your end without contacting support.

Authentication and rate limits

How the wire is secured

Authentication is a single Bearer token per installation. There is no OAuth handshake, no per-request signing, no client-side cert. Operational simplicity is intentional — devs should not need a 30-page integration guide to make the first authenticated call.

01License key

Bearer token in the Authorization header

Each installation receives one license key (UUID v4). Send it as a standard Bearer token on every request. Keys are revocable on request — flipping bdapi_clients.is_active = false invalidates the key immediately.

Request header

Authorization: Bearer $LICENSE_KEY
02Rate limits

No hostile throttle today; abuse triggers manual review

/api/versions/* is designed for daily polling per installation. There is no rate limit enforced on the authenticated surface today — up to once per minute is fine for integration testing. The public /api/leads endpoint (used by this marketing site) is rate-limited per IP and per email. Adaptive per-license rate limits are on the roadmap (see code).

Rate-limit policy (Q3 2026 roadmap)

# /api/versions/* — designed for daily polling per installation.
# No throttle enforced today; up to once per minute is fine for testing.
# Heavy abusive patterns may trigger manual review by BigHubAI ops.

# Public /api/leads (used by this landing page) is rate-limited per IP
# and per email — returns HTTP 429 with Retry-After when exceeded.

# Roadmap (Coming Q3 2026): per-license-key adaptive rate limit
# with response headers X-RateLimit-Remaining and X-RateLimit-Reset.
03Rotation

Rotation via support — 24h overlap on the roadmap

Today, license-key rotation is requested via the support channel. The planned policy is self-service rotation with a 24-hour overlap window where both the old and the new key authenticate — letting you roll deployments without coordinating a hard cutover. ETA Q3 2026.

Rotation flow (Q3 2026 roadmap)

# 1. Request a new key via support (replaces the admin UI flow,
#    which is internal to BigHubAI ops).
# 2. Roll your deployment with the new $LICENSE_KEY.
# 3. Old key remains active during the overlap window.

# Roadmap (Coming Q3 2026): self-service rotation with a 24h
# overlap window where both old and new keys authenticate.
04Scope

License keys are installation-scoped

A license key authenticates exactly one installation; every query filters by client_id derived from the bearer token. For multi-tenant integrations, request one key per tenant if you need isolation, or one key with tenant attribution handled inside your application. Explicit 403 forbidden_scope responses for cross-installation attempts are on the roadmap.

Scope enforcement

GET /api/versions/check
Authorization: Bearer $LICENSE_KEY_TENANT_A

# Returns version state for installation A only — every query
# filters by client_id derived from the bearer token automatically.

# Roadmap (Coming Q3 2026): explicit 403 forbidden_scope response
# when a request tries to reach data outside its installation.

Webhook payload

What lands on your endpoint

Every event is a JSON POST with attestation headers. EC Publications and the Regulatory Watch subsystem (SCCS, Safety Gate, EUR-Lex — shown below) share the same X-BDAPI-* header namespace; they are told apart by the X-BDAPI-Event value (e.g. `ec.publication.detected` vs `watch.safety_gate.alert.analyzed`). The body is the canonical event document, including AI-enriched analysis when available. Failures on your end (5xx, timeout) trigger the retry policy automatically.

Sample payload — Safety Gate alert (Watch subsystem)

{
  "event": "watch.safety_gate.alert.analyzed",
  "event_id": "01HQZK9YBV-7M8K-3R2N-0P4S-5T6U7V8W9X0Y",
  "source_engine": "safety_gate",
  "source_id": 12345,
  "tier": "premium",
  "timestamp": "2026-05-15T08:32:11.000Z",
  "item": {
    "alert_number": "A12/01234/26",
    "report_id": "12345",
    "product_name": "Cosmetic moisturiser, brand X",
    "brand": "Brand X",
    "risk_type": "Chemical",
    "risk_level": "serious",
    "notifying_country": "DE",
    "country_of_origin": "CN",
    "source_url": "https://ec.europa.eu/safety-gate-alerts/screen/webReport/alertDetail/12345",
    "published_at": "2026-05-15T00:00:00Z",
    "detected_at": "2026-05-15T08:00:00Z"
  },
  "analysis": {
    "criticality": "CRITICAL",
    "summary_es": "Notificación A12/01234/26: producto leave-on excede el límite Annex V de MIT (0.0015 %). Estado miembro notificante: Alemania. Sin período de transición — retirada recomendada para SKUs afectados.",
    "risk_assessment": "Concentración de MIT detectada por encima del límite regulatorio en formulación de aplicación prolongada.",
    "regulatory_implications": "Annex V entry 57 — MIT prohibido en leave-on desde el Reglamento (UE) 2017/1224.",
    "cross_market_relevance": "Relevante para los 27 EM. Producto en e-commerce paneuropeo.",
    "recommended_action_for_brand_owners": "Auditar fórmulas, retirar lotes afectados, notificar PCPC nacional.",
    "needs_human_review": false,
    "confidence": 0.92,
    "model": "claude-sonnet-4-5"
  }
}

Sample payload — EUR-Lex act (Watch subsystem)

{
  "event": "watch.eurlex.act.analyzed",
  "event_id": "01HQZM4P7R-2K9D-4N8T-1B6S-7C0V9W2X3Y4Z",
  "source_engine": "eurlex",
  "source_id": 4821,
  "tier": "premium",
  "timestamp": "2026-05-20T06:15:42.000Z",
  "item": {
    "id": 4821,
    "celex_number": "32026R0712",
    "title": "Commission Regulation (EU) 2026/712 amending Annex V to Regulation (EC) No 1223/2009 as regards the maximum concentration of a preservative",
    "act_type": "regulation",
    "date_document": "2026-05-18",
    "eli_uri": null,
    "source_url": "https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32026R0712",
    "detected_at": "2026-05-20T06:00:00Z"
  },
  "analysis": {
    "criticality": "HIGH",
    "summary_es": "Reglamento (UE) 2026/712: modifica el Anexo V para reducir la concentración máxima de benzoato de sodio en productos leave-on. Período de transición hasta el 01/12/2026. Revisar fórmulas con conservantes del Anexo V.",
    "substances": [
      {
        "name": "Sodium benzoate",
        "cas": "532-32-1",
        "max_concentration": "2.5% (leave-on)",
        "proposed_action": "restricted"
      }
    ],
    "key_dates": [
      { "type": "entry_into_force", "date": "2026-06-07" },
      { "type": "application_deadline", "date": "2026-12-01" }
    ],
    "needs_human_review": false,
    "confidence": 0.90,
    "model": "claude-sonnet-4-5"
  }
}

Illustrative sample. CELEX is the stable identifier used for deduplication and source linking; eli_uri is reserved and currently null. Beyond date_document, the affected substances, CAS numbers and critical dates (entry into force, application deadline) are extracted from the act text and surfaced in the analysis block — key_dates and substances.

Full HTTP request (headers + body excerpt)

POST /webhooks/bdapi HTTP/1.1
Host: your-app.example
Content-Type: application/json
X-BDAPI-Event:     watch.safety_gate.alert.analyzed
X-BDAPI-Timestamp: 1747297931
X-BDAPI-Signature: sha256=9f2c4d1e8a7b3f6c2e8d1a5b4c7e9f0a2b3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f
X-BDAPI-Delivery:  00000000-0000-0000-0000-000000000000
User-Agent:        BDAPI-Watch/1.0 (+https://bdapi.app)

{ "event": "watch.safety_gate.alert.analyzed", "source_engine": "safety_gate", ... }

Attestation headers — unified X-BDAPI-* namespace

X-BDAPI-Event
Full event name and the field you route on. Format: `watch.{engine}.{noun}.{type}` for Watch (e.g. `watch.safety_gate.alert.analyzed`) or `ec.publication.detected` for EC Publications. The receiver should ignore unknown event types and return 200.
X-BDAPI-Timestamp
Unix epoch in SECONDS (not milliseconds, not ISO 8601). Used to construct the signature. Reject requests older than 5 minutes to defend against replay.
X-BDAPI-Signature
HMAC-SHA256 hex digest of `timestamp + "." + raw_body`, keyed with your webhook secret. Format: `sha256=<hex>`. NOTE: the signed string is the concatenation, not the body alone.
X-BDAPI-Delivery
UUID v4 unique per delivery attempt, sent on Watch dispatches (not EC Publications). Use it as a unique constraint on your inbound table so retries become no-ops. Mirrored in `payload.event_id`.

Footgun

Note: BD-API signs `timestamp + "." + raw_body`, NOT the body alone. And it signs the raw bytes of the body — if your framework JSON-parses before exposing the body, capture the raw payload before parsing. Re-serialising and then hashing will not match the signature. EC Publications and Watch use the identical X-BDAPI-Event / X-BDAPI-Timestamp / X-BDAPI-Signature headers and the same algorithm — only Watch dispatches add X-BDAPI-Delivery, and you branch on the X-BDAPI-Event value.

Signature verification

Verify the HMAC before you trust the payload

Four reference implementations — Node, Python, Go, PHP. Each one does the same three things: enforce a 300-second replay window, recompute HMAC-SHA256 over `timestamp + "." + raw_body` with your shared secret, and compare in constant time. EC Publications and Regulatory Watch dispatches share the same X-BDAPI-* headers — tell them apart downstream using the X-BDAPI-Event value, not a different header set. Drop these functions into your webhook handler.

verify.mjsNode.js
import crypto from 'node:crypto'

// Express: capture the raw body BEFORE JSON parsing.
// app.use(express.json({ verify: (req, _res, buf) => { req.rawBody = buf } }))

export function verifyBdApiSignature(req, secret) {
  // EC Publications and Regulatory Watch dispatches share the same
  // X-BDAPI-* headers. Route by the X-BDAPI-Event value downstream
  // (e.g. "ec.publication.detected" vs "watch.sccs.item.detected") —
  // signature verification itself is identical for both.
  const ts        = req.header('X-BDAPI-Timestamp') || ''
  const sigHeader = req.header('X-BDAPI-Signature') || ''

  // Replay protection — same 300s window for every event.
  if (Math.abs(Math.floor(Date.now() / 1000) - parseInt(ts, 10)) > 300) {
    return false
  }

  const received = sigHeader.replace(/^sha256=/, '')

  // CRITICAL: the signed message is `timestamp + "." + body`, NOT the body alone.
  const message  = ts + '.' + req.rawBody.toString('utf8')
  const expected = crypto.createHmac('sha256', secret).update(message).digest('hex')

  const a = Buffer.from(received, 'hex')
  const b = Buffer.from(expected, 'hex')
  if (a.length !== b.length) return false

  // Constant-time compare — NEVER use a === b for signatures.
  return crypto.timingSafeEqual(a, b)
}

Footgun

Never compare signatures with `==` or `bytes.Equal`. Use crypto.timingSafeEqual (Node), hmac.compare_digest (Python), hmac.Equal (Go), or hash_equals (PHP). String/byte equality short-circuits on the first mismatched byte, which leaks the position of the differing byte through timing — enough for a determined attacker to forge a signature. And never sign the body alone — the message is timestamp concatenated with the body via a literal period.

Test license and sandbox practices

Test against real production behavior, not a stubbed mock

We do not run a separate sandbox installation. Instead, we issue a dedicated evaluation license key that points to the same production environment — same code paths, same signing logic, same retry policy. What you exercise against it is exactly what runs in prod.

  1. 01
    Evaluation license keyComing Q3 2026

    Same environment as production

    Provisioned manually on request. Authenticates identically to production keys but the test mode flag (Coming Q3 2026) sends X-BDAPI-Mode: test on outbound webhooks and skips commercial counters on feedback. Audit trail is preserved. No artificial rate limits during integration.

  2. 02
    Local receiver via tunnel

    ngrok / Cloudflare Tunnel for local webhook testing

    Expose your localhost over HTTPS with ngrok, Cloudflare Tunnel or localtunnel. Hand the temporary URL to BigHubAI for registration against your test license; real dispatches land on your laptop. Best path for the first development cycle.

  3. 03
    Replayable offline testing

    Capture, re-sign, replay against dev

    Capture a real dispatch payload from your staging receiver, regenerate the HMAC with your dev secret using the replay-firma.sh script in the pack, and POST it against your local dev. Lets you iterate on parsing and idempotency without waiting for a real upstream event.

  4. 04
    Onboarding

    Manual issuance, no fixed expiry

    The evaluation license key is issued during the commercial discussion and stays active for as long as you need it to build — there is no automatic expiration. When you are ready to move to production, request your production key through the same channel; if you would rather keep the same key, our team can rotate it with a short grace window (old and new key both valid) so nothing breaks mid-cutover.

Regulatory events API · P3 · Premium tier

A regulatory events API that never asks for your formulation

A GET-only REST surface over the same canonical substance dictionary BD-API maintains internally, plus a webhook stream of confirmed and reconciled regulatory events. Authenticate with the same Bearer license_key used across the rest of the API. No endpoint accepts a body, and no response includes a compliance verdict — matching a restriction against your formulation happens entirely on your side.

Endpoints

GET /api/regulatory/meta
Dictionary version, row counts and freshness timestamps. Supports ETag / conditional 304, so polling it on a schedule costs almost nothing.
GET /api/regulatory/substances
Canonical substance identity dictionary — around 28,000 rows — with keyset pagination.
GET /api/regulatory/substances/:canonical_id
One substance: its identities, its group and its current restrictions.
GET /api/regulatory/restrictions
Active restrictions, cursor-paginated.
GET /api/regulatory/events?since=
Confirmed and reconciled regulatory events since a given point in time.
GET /api/regulatory/events/:id
A single event — the target of the ref field in the webhook payload below.

Privacy by construction

No endpoint accepts a body or ingests client product or formulation data. No response includes a compliance verdict. Whether a restriction applies to your product is a match you run client-side, inside your own systems — BD-API never sees what you make.

Webhook stream

regulatory.event.confirmed and regulatory.event.reconciled are dispatched as HMAC-SHA256-signed webhooks — the same header family as everywhere else (X-BDAPI-Event, X-BDAPI-Timestamp, X-BDAPI-Signature), with at-least-once delivery and exponential backoff retries.

Sample payload — regulatory.event.reconciled

{
  "event": "regulatory.event.reconciled",
  "payload_version": 1,
  "event_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "delivered_at": "2026-07-02T12:00:05.000Z",
  "regulatory_event": {
    "id": 891,
    "status": "reconciled",
    "canonical_id": 1042,
    "proposed_action": "ban",
    "limit": {
      "kind": "concentration_ceiling",
      "value_pct": "1.000000",
      "unit": "pct",
      "product_scope": "leave-on"
    },
    "celex_number": "32024R0996",
    "date_force": "2025-03-01",
    "lead_time_days": 337
  },
  "ref": "/api/regulatory/events/891",
  "signed_at": "2026-07-02T12:00:05.000Z"
}

For developers

Common technical questions

Is there an OpenAPI spec?+

Yes. We ship an OpenAPI 3.1 spec covering every public endpoint plus the webhook payloads. It is delivered as part of the full integration pack after we review your request — drop it into Postman, Insomnia, openapi-generator, oapi-codegen or any tool that consumes OpenAPI. The full pack is sent to qualified integrators only; request access via the contact form.

Do I need an SDK or can I use raw HTTP?+

Raw HTTP is fully supported and the recommended path. No SDK lock-in. The pack ships executable code samples in curl, Node.js, Python, Go (signature verification) and PHP — drop them into your project and ship. Any HTTP client (axios, fetch, requests, httpx, Guzzle, Go net/http) works.

How do I receive webhooks during local development?+

Use ngrok, Cloudflare Tunnel or localtunnel to expose your localhost over HTTPS. For replayable offline testing, the pack includes a `replay-firma.sh` script that re-signs a captured payload with your dev secret so you can iterate without waiting for a real dispatch.

What happens if my webhook endpoint is down for hours?+

BD-API retries with exponential backoff (1s / 2s / 4s, 3 attempts) per dispatch. After full failure, the dispatch row stays in the failed pool and the next CRON tick re-evaluates it as long as `retry_count < 3`. After permanent failure, the event is queryable via the REST API so you can backfill manually. Receivers should be idempotent — the same `event_id` may arrive more than once.

How is the license key scoped?+

Each license key authenticates exactly one installation. Cross-installation queries are not addressable. For multi-tenant integrations, request one key per tenant if you need isolation at the BD-API layer, or one key with tenant attribution handled inside your application.

What about rate limits on the /api/versions/* endpoints?+

Designed for daily polling per installation. There is no hostile rate limit on this surface today — higher-frequency polling is supported (up to once per minute is fine for integration testing) but production patterns should stay close to daily, because the upstream CosIng source itself does not update faster than that.

How many regulatory sources does BD-API cover?+

Eight sources across three integration surfaces. CosIng is delivered as signed incremental SQL patches via REST pull. Seven watch sources — EC Publications (Official Journal EU via EC Drupal), SCCS opinions, Safety Gate alerts, EUR-Lex acts, IFRA standards, ECHA SVHC and ECHA CLP — are delivered as HMAC-signed webhooks. Premium installations also get the regulatory events API described above: a GET-only pull surface plus regulatory.event.* webhooks. You can subscribe per-source — you do not have to take all of them to take one. How each source fits into the regulatory monitoring playbook →

How long does a typical integration take?+

A working integration against a sandbox license key — webhook receiver verifying HMAC, daily CosIng patch cron applying inside a single PostgreSQL transaction, feedback loop closed — is 4 to 12 engineering hours with the pack we provide. Production hardening (alerting, audit logging, multi-tenant attribution) is on top of that and depends on your stack.

Do you offer a sandbox or test license?+

Yes. Evaluation license keys are issued manually by our team on request and point to the same production environment — same code paths, same signing logic, same retry policy — so what you build against it is what runs in production. There is no separate sandbox deployment and no automatic expiration: the key stays active until our team deactivates it (or you ask us to), and when you are ready to go live we issue your production key through the same channel.

What happens if BD-API stops operating?+

The CosIng data you have already applied lives in your own PostgreSQL — you keep it. Patches are pure SQL with no proprietary runtime. Webhook events you received are in your own database. There is no vendor-managed runtime to retire, no SDK that stops working, no opaque binary protocol that locks you in. We give you durability by design, not by promise.

Why sign webhooks with HMAC if I'm already using HTTPS?+

HTTPS protects the transport — it prevents someone on the network from reading or tampering with the payload in transit. HMAC protects the origin — it lets your receiver prove that the payload actually came from BD-API and not from anyone who learned your endpoint URL. Without HMAC, any attacker who guesses or discovers your webhook URL can forge events. HTTPS without HMAC is encryption without authentication, and authentication is the part that matters for compliance evidence. Five-minute HMAC verification guide with code samples →

What is a timing attack and why does it matter when verifying HMAC signatures?+

A timing attack exploits the fact that a naive string comparison (== or ===) returns as soon as it finds a mismatched character. An attacker who can measure response times can deduce signature bytes one by one until they forge a valid one. The fix is constant-time comparison — crypto.timingSafeEqual in Node, hmac.compare_digest in Python, hash_equals in PHP. The cost is identical to a normal comparison; the safety gain is total. Common HMAC verification mistakes — including timing comparisons →