Provenance
Inbound Webhooks

Mappings

Define how webhook payloads are transformed into provenance interactions.

An inbound mapping defines how a specific type of webhook payload from a source becomes a provenance interaction. A single source can have multiple mappings — each matching different event types.

Creating a mapping

POST /api/inbound-mappings
{
  "sourceId": "uuid",
  "title": "Stripe Checkout Completed",
  "description": "Maps completed checkout sessions to ORDER CREATE interactions",
  "resourceTypeId": "uuid",
  "actionId": "uuid",
  "resourceIdPath": "$.data.object.id",
  "idempotencyPath": "$.id",
  "priority": 10,
  "filter": {
    "conditions": [
      { "field": "$.type", "operator": "equals", "value": "checkout.session.completed" }
    ]
  },
  "fieldMappings": {
    "customerEmail": "$.data.object.customer_email",
    "amount": "$.data.object.amount_total",
    "currency": "$.data.object.currency"
  },
  "metadataPaths": {
    "userId": "$.data.object.customer",
    "sessionId": "$.data.object.id"
  },
  "payloadSchema": { ... },
  "schemaRootPath": "$.data.object",
  "isActive": true
}

Fields

FieldRequiredDescription
sourceIdYesWhich inbound source this mapping belongs to
titleYesDisplay name
descriptionNoWhat this mapping does
resourceTypeIdYesResource type for the created interaction
actionIdYesAction for the created interaction
resourceIdPathYesJSONPath to extract the resource ID from the payload
idempotencyPathNoJSONPath for deduplication — if an interaction with this value already exists, the webhook is skipped
priorityNoHigher priority mappings are matched first (default: 0)
filterNoConditions to match specific payloads
fieldMappingsNoMap payload paths to interaction metadata fields. If omitted, the entire payload is used as the interaction object
metadataPathsNoMap payload paths to interaction root-level fields (userId, sessionId, uowId, etc.)
payloadSchemaNoAJV schema for mapping-level validation
schemaRootPathNoJSONPath to a sub-object for schema validation (e.g. $.data.object)
isActiveYesWhether this mapping is active

Filter conditions

Filters determine which payloads match this mapping. All conditions must match (AND logic).

{
  "conditions": [
    { "field": "$.type", "operator": "equals", "value": "checkout.session.completed" },
    { "field": "$.data.object.status", "operator": "equals", "value": "complete" }
  ]
}

Supported operators

OperatorDescription
equalsExact match
not_equalsNot equal
containsString contains
starts_withString starts with
ends_withString ends with
gtGreater than
gteGreater than or equal
ltLess than
lteLess than or equal
inValue is in array
existsField exists (non-null)

Field mappings

Field mappings extract values from the webhook payload into the interaction's metadata object. Keys are the target field names, values are JSONPath expressions.

{
  "customerEmail": "$.data.object.customer_email",
  "amount": "$.data.object.amount_total",
  "currency": "$.data.object.currency",
  "lineItems": "$.data.object.line_items"
}

When no field mappings are defined, the entire webhook payload is stored as the interaction object.

Metadata paths

Metadata paths map payload fields to interaction root-level fields. These are stored with a _ prefix in the interaction JSONB.

{
  "userId": "$.data.object.customer",
  "sessionId": "$.data.object.id",
  "uowId": "$.data.object.payment_intent",
  "interactionTimestamp": "$.created",
  "externalReference": "$.id"
}
KeyDescription
userIdUser who triggered the event
sessionIdSession identifier
uowIdUnit of Work ID for correlation — auto-generates a UUID if not mapped
interactionTimestampOriginal event timestamp
externalReferenceExternal system reference ID

Schema validation

Mappings can define their own AJV schema, independent of the source-level schema. This is useful when different event types within the same source have different structures.

Schema root path

Use schemaRootPath to validate a nested sub-object instead of the full payload:

{
  "payloadSchema": {
    "type": "object",
    "required": ["id", "customer_email"],
    "properties": {
      "id": { "type": "string" },
      "customer_email": { "type": "string" }
    }
  },
  "schemaRootPath": "$.data.object"
}

This validates payload.data.object against the schema, not the entire payload. Useful for services like Stripe where the interesting data is nested inside a wrapper.

Priority and matching

When multiple mappings match a payload, the one with the highest priority value wins. Use this to create specific mappings that override general ones:

MappingFilterPriority
Stripe Checkout$.type equals checkout.session.completed10
Stripe Payment$.type starts_with payment_intent5
Stripe Catch-All(no filter)0

Testing mappings

Test a mapping against a sample payload without creating an actual interaction:

POST /api/inbound-mappings/:id/test
{
  "payload": {
    "id": "evt_123",
    "type": "checkout.session.completed",
    "data": {
      "object": {
        "id": "cs_123",
        "customer_email": "user@example.com",
        "amount_total": 2999
      }
    }
  }
}

The response shows:

  • Whether the source schema passed
  • Whether the mapping schema passed (with error details if not)
  • Whether the filter conditions matched
  • The interaction that would be created (resource ID, metadata, field values)

Example: Stripe checkout

Source

{
  "title": "Stripe",
  "mnemonic": "STRIPE",
  "originId": "...",
  "verification": {
    "algorithm": "hmac-sha256",
    "signatureHeader": "stripe-signature",
    "encoding": "hex"
  },
  "verifySecret": "{{secret.stripeWebhookSecret}}",
  "payloadSchema": {
    "type": "object",
    "required": ["id", "type", "data"],
    "properties": {
      "id": { "type": "string", "pattern": "^evt_" },
      "type": { "type": "string" },
      "data": { "type": "object" }
    }
  }
}

Mapping

{
  "title": "Checkout Completed",
  "resourceTypeId": "...",
  "actionId": "...",
  "resourceIdPath": "$.data.object.id",
  "idempotencyPath": "$.id",
  "priority": 10,
  "filter": {
    "conditions": [
      { "field": "$.type", "operator": "equals", "value": "checkout.session.completed" }
    ]
  },
  "fieldMappings": {
    "email": "$.data.object.customer_email",
    "amount": "$.data.object.amount_total",
    "currency": "$.data.object.currency"
  },
  "metadataPaths": {
    "userId": "$.data.object.customer",
    "externalReference": "$.id"
  },
  "schemaRootPath": "$.data.object",
  "payloadSchema": {
    "type": "object",
    "required": ["id", "customer_email"],
    "properties": {
      "id": { "type": "string", "pattern": "^cs_" },
      "customer_email": { "type": "string" }
    }
  }
}