Inbound Webhooks

Inbound webhooks let external systems push data into Rekor (data in). Each one provides a unique URL endpoint — when an external service POSTs JSON to it, Rekor automatically creates or updates a document in the target collection.

How It Works

  1. Create an inbound webhook — specify the target collection and optional data mapping.
  2. Rekor generates a unique URL — share this with the external system (e.g., PagerDuty, GitHub, Stripe).
  3. External system POSTs data — Rekor validates the payload and creates a document.

Creating an Inbound Webhook

rekor inbound-webhooks create --database my-database \
  --name "PagerDuty Incidents" \
  --secret <hmac-secret> \
  --collection-scope incidents

This returns an inbound webhook with a unique ingest URL:

POST /inbound/{org_id}/{database_id}/{inbound_webhook_id}/ingest

Managing Inbound Webhooks

# List all inbound webhooks
rekor inbound-webhooks list --database my-database

# Get a specific inbound webhook
rekor inbound-webhooks get {inbound_webhook_id} --database my-database

# Delete an inbound webhook
rekor inbound-webhooks delete {inbound_webhook_id} --database my-database

Ingest Endpoint

External systems POST JSON to the inbound webhook's ingest URL. The payload is validated against the target collection's schema and stored as a document.

curl -X POST /inbound/{org_id}/{ws}/{inbound_webhook_id}/ingest \
  -H "Content-Type: application/json" \
  -d '{"title":"API spike","severity":"p1"}'

Mapping the Payload

By default the received payload is stored as-is. To store canonical documents straight from an external system's raw shape, attach a mapping — the same field-mapping contract external sources use (renames, value maps, date reformatting, compose), applied on the way in:

  • Reuse a source's mapping--source-binding '{"collection":"<id>","source":"<name>"}' reuses an existing source's field mapping, so the same translation that proxies that collection's reads and writes also canonicalizes inbound deliveries — one contract, both directions.
  • Inline mapping--field-mapping '{"to_external":{"status":"state"}}' for a purely-native collection with no source. Renames auto-invert on read (upstream state → Rekor status); or give an explicit to_rekor/computed mapping.

The two are mutually exclusive, and the mapping is validated both when the inbound webhook is created and again at promotion. Paired with an external-write trigger, this keeps a native collection in sync with a plain-HTTP upstream in both directions from a single source contract — no executor.

Authentication

By default the sender signs each request and Rekor verifies an HMAC signature. For senders that authenticate with a static per-account header instead of signing every request, configure that scheme explicitly:

rekor inbound-webhooks create --database my-database \
  --name "Account Events" --secret <token> \
  --collection-scope events \
  --ingest-auth '{"type":"static_header","header":"X-Account-Key"}'

Rekor then compares the configured header's value against the secret (constant-time) rather than verifying a signature — letting you accept webhooks from systems that send a fixed account key.

Hydrating Webhooks (notification + fetch)

Many systems send reference-style webhooks — "record X changed, here is its id" — instead of the full record (Stripe recommends re-fetching by id; Shopify, Salesforce, HubSpot and legacy CRMs do the same). Point such a webhook at a collection's read source and Rekor will fetch the full record itself on each delivery, then store the canonical document:

rekor inbound-webhooks create --database my-database \
  --name "Orders (reference)" --secret <token> \
  --collection-scope orders \
  --source-binding '{"collection":"orders","source":"shop_api"}' \
  --ingest-auth '{"type":"static_header","header":"X-Account-Key"}' \
  --hydration '{"id_path":"record_id","event_path":"event","event_map":{"ResourceDeleted":"delete"}}'

On a delivery, Rekor reads the record id from the thin body (id_path), calls the bound source's read endpoint to pull the full record, canonicalizes it through the source's field mapping, and upserts it by external id — the inbound twin of a proxied read. The event field (event_path + event_map) routes to upsert or delete (a delete skips the fetch and removes the mirrored record); anything else defaults to upsert. The fetch and write happen asynchronously with automatic retry and backoff, so the sender gets an immediate accept — check progress with rekor inbound-webhooks deliveries --database my-database.

Hydration requires a --source-binding (it provides the read endpoint) and a single native --collection-scope. For security, Rekor only ever fetches the source-configured URL with the extracted id — a callback URL inside the payload is ignored, never followed.

By default each re-sync replaces the whole document, so any field you added locally — a workflow status, tag, score, assignment, or note the upstream system doesn't have — is overwritten. Add "merge":true to --hydration and a re-sync refreshes only the fields the source's mapping owns and preserves everything else you put on the document. That lets one collection hold both the upstream mirror and your own enrichment, so you can still list and filter the entity by your own field (for example status=qualified). The first sync still creates the record from the mapped fields, a field the upstream later clears is reflected, and the merged document is still schema-validated.

--hydration '{"id_path":"record_id","merge":true}'

Inbound-Webhook-Originated Writes

Documents created via inbound webhooks carry an X-Rekor-Source: inbound_webhook header. Triggers with skip_inbound_webhook_writes: true (the default) won't re-fire on these writes, preventing infinite loops between inbound webhooks and triggers.

Environment Restrictions

Inbound webhooks can only be created or deleted in preview databases. To add inbound webhooks to production, create them in a preview database and promote.

Inbound Webhooks — Rekor