Skip to main content

Export Destinations API

/v1/integrations/destinations is the unified surface for every place Quint ships your organization’s data, across both delivery lanes:
  • Batch lane — the SIEM exporter periodically delivers matched events to s3, splunk_hec, and webhook destinations.
  • Realtime lane — the alert processor delivers individual alerts to alert_webhook destinations as they fire, signed with a per-destination HMAC key.
All endpoints require an admin-role JWT (Authorization: Bearer <JWT> — see Authentication).
The legacy /v1/integrations/siem endpoints remain fully supported as a byte-compatible alias over the same data, restricted to the three batch types. New integrations should use /v1/integrations/destinations.

Endpoints

The destination object

  • type — one of s3, splunk_hec, webhook, alert_webhook. Further types (datadog, sentinel, elastic, sumo, s3_parquet) will be accepted only when their delivery adapter ships.
  • formatjson (default), cef, ocsf, or parquet.
  • config — per-type settings (see per-type setup). Responses return a redacted projection: non-secret values in full, secret-bearing values replaced with a marker (key names survive, so you can confirm which fields you configured).
  • delivery_statedisabled, unknown, pending_delivery, failing, credential_invalid, awaiting_events, or active, with a sentence of explanation in delivery_detail.
credential_invalid means the destination is rejecting the configured credential (401/403). Deliveries are paused rather than retried against a dead key; write a new credential (batch types) or rotate the signing key (alert_webhook) to resume. alert_webhook rows report unknown platform delivery state — the realtime lane has no batch-exporter heartbeat, so batch liveness is deliberately not claimed for them.

credential_ref — write-only credential semantics

Every destination row carries a credential_ref describing how it authenticates, in one of four modes: Credential values are write-only:
  • You supply a credential on POST/PUT (e.g. config.token for Splunk HEC). It is never echoed back — not in the create response, not on any subsequent read.
  • Reads return metadata only: mode, key/slot version numbers, fingerprint_sha256, last4, expires_at, credential_updated_at, last_verified_at, grace_until — enough to confirm which credential is configured and whether it is due for rotation, never enough to recover it.
  • For alert_webhook, the signing secret is revealed exactly once, in the 201/rotate response body. Quint does not store it and cannot show it again. No reveal endpoint exists; rotation is the only way to obtain a new secret.
  • credential_ref cannot be changed through the plain PUT update — attempting to send one returns 400. Credential state moves only through the dedicated lifecycle paths (rotate, credential writes).

Fingerprint check

GET /v1/integrations/destinations/{id}/fingerprint is the support surface: it returns the credential metadata including fingerprint_sha256 — the SHA-256 of the signing key — so you and Quint support can confirm “the key you hold matches the key we would derive” without either side ever transmitting the key.
To compute your side: base64-decode the portion of your secret after the whsec_ prefix, SHA-256 it, and compare the hex.

Rate limits

Three endpoints mint or exercise credentials and are deliberately budgeted per hour; everything else is unlimited: Over-budget requests return 429 with Retry-After; responses carry X-RateLimit-Limit and X-RateLimit-Remaining. Delete and disable are never rate-limited or gated — removing a destination whose credential leaked must always work immediately.

Per-type setup

Splunk HEC (splunk_hec)

Recommended Splunk-side setup:
  1. Create a dedicated HEC token for Quint — never reuse a token shared with other senders, so revoking Quint’s access never breaks anything else and Splunk-side audit attributes traffic correctly.
  2. Restrict the token with allowedIndexes to the single index Quint should write (e.g. quint), so a leaked token cannot write anywhere else.
  3. To rotate: create a new token in Splunk, PUT the destination with the new config.token, verify delivery, then delete the old token in Splunk. Rotation is customer-driven — there is no Quint-side rotate for vendor-held credentials.
token is write-only: reads return it redacted. Delivery uses Authorization: Splunk <token> against /services/collector/event.

Batch webhook (webhook)

Quint POSTs a JSON array of events to config.url, attaching any config.headers you supply verbatim.
Batch webhook deliveries are unsigned. The headers you supply are the only origin authentication on this lane — a receiver that does not validate them has no way to distinguish Quint’s deliveries from anyone else’s POST. Treat header values as bearer secrets (they are write-only and redacted on read). If you need signed deliveries, use the alert_webhook lane for alerts; signed batch delivery is a planned signature_scheme addition.
The URL must be HTTPS and publicly resolvable; deliveries to private, link-local, or metadata addresses are refused, and redirects are never followed.

S3 (s3)

Phase 1 s3 destinations deliver from Quint’s exporter using its own AWS identity; grant the exporter write access to your bucket out-of-band with your Quint contact. The cross-account sts:AssumeRole flow (s3_parquet, credential_ref.mode: "federated" with a Quint-generated ExternalId) — including the customer trust-policy snippet — ships with Phase 2.

Alert webhook (alert_webhook)

The realtime lane: signed, per-alert HTTP deliveries to your receiver.
The 201 response is the destination object plus, once and never again:
Setup flow — create → configure receiver → test → enable:
  1. Create the destination (it defaults to enabled: false — the secret is first revealed in the create response, so no receiver can have verified a delivery yet).
  2. Store the whsec_ secret in your receiver and implement signature verification.
  3. POST /destinations/{id}/test — Quint sends a signed synthetic quint.test.ping alert (payload carries "test": true) over the exact production wire path and records the outcome on the row.
  4. PUT the destination with "enabled": true. Enabling requires a passed test; pass "skip_test": true to enable untested (not recommended).
config.url must be HTTPS, without embedded credentials, and must not resolve to a private or metadata address.

Rotating the signing key

Rotation is never gated (it is how you revoke a leaked secret) and works as follows:
  • The response reveals the new secret (whsec_…) once, with the new key_version.
  • For a 72-hour grace window (grace_until in the credential metadata), deliveries keep signing X-Quint-Signature with the previous key — the one your receiver already verifies — while the new key’s signature rides in the separate X-Quint-Signature-Next header. Update your receiver to the new key any time within the window.
  • At grace_until the previous key is dropped automatically — no confirmation step. Deliveries then sign X-Quint-Signature with the new key only.
  • Back-to-back rotations supersede any running grace window: exactly one previous key is ever honoured.

Verifying webhook signatures

This section is the wire contract for alert_webhook deliveries.

The delivery

Each alert is POSTed to <config.url>/alerts/<alert_type> — for example https://alerts.example.com/quint/alerts/divergence — with a JSON body:

The headers

Verification steps

  1. Read the raw request body bytes — do not re-serialize parsed JSON; any re-encoding changes the bytes and the MAC.
  2. Base64-decode your stored secret after the whsec_ prefix to get the key.
  3. Compute HMAC-SHA256(key, body) and render as sha256= + lowercase hex.
  4. Compare against X-Quint-Signature with a constant-time comparison (hmac.compare_digest, crypto.timingSafeEqual, Go’s hmac.Equal).
  5. If it does not match and X-Quint-Signature-Next is present, compare against that header with your new key — a match means a rotation is in progress and you should finish switching to the new secret before the grace window ends.
  6. Reject the delivery (non-2xx) on no match; Quint retries failed deliveries up to 3 times with backoff.
X-Quint-Timestamp is advisory and provides no replay protection. The signature covers the request body only — the timestamp is not included in the MAC, so anyone who captures a delivery can rewrite the timestamp freely and the signature still verifies. You may use it as a freshness heuristic (e.g. flag deliveries more than ±5 minutes old for review), but do not build replay protection on it. For replay protection today, deduplicate on payload identity as described below; an authenticated-timestamp scheme (v2) is planned — see signature schemes.

Duplicate deliveries and idempotency

Alert delivery is at-least-once: the upstream queue can redeliver a message, and failed HTTP attempts are retried. Quint’s own delivery log deduplicates internally on a publish-stable claim key, but that key is not exposed on the wire, so receivers should make processing idempotent on the payload:
  • Do not key idempotency on alert_id alone — it is minted per evaluation, so a queue redelivery of the same underlying event can arrive with a different alert_id.
  • Key on (event_id, type, subtype) instead: event_id is stable for the underlying event, and one event can legitimately trigger alerts of more than one type.

Signature schemes

Destinations carry a reserved signature_scheme config field describing the signing contract above:
  • v1 (the only value today, and the default when absent) — the scheme documented on this page: body-only HMAC-SHA256, X-Quint-Signature/X-Quint-Timestamp/X-Quint-Key-Version, unauthenticated timestamp.
  • v2 (planned) — an authenticated-timestamp scheme in the Standard-Webhooks style: the MAC is computed over the timestamp and the body under the same key-version epoch, closing the replay gap described above. Once shipped, v2 will be the recommended default for all new destinations; v1 remains supported for existing receivers.
A destination uses exactly one scheme; a single row is never dual-format. Because changing the MAC input is receiver-breaking, existing destinations will only move to v2 deliberately, via rotation-style opt-in — never silently.

Errors