Webhook Ingest ExecModule
Overview
WebhookIngestModule converts one bounded JSON webhook payload into a deterministic event proposal for downstream workflow steps. Version 2 replaces the legacy implementation that generated random Contact and EngagementEvent IDs without persisting either record. It also stops returning the complete raw payload and makes the absence of persistence explicit.
The canonical module identifier is:
com.valkyrlabs.workflow.modules.marketing.WebhookIngestModule
This module is a local normalization boundary. It does not expose an HTTP endpoint, verify a provider signature, create a Contact, persist an EngagementEvent, or grant access to business data.
Usage
- Receive and authenticate the provider webhook through a trusted ingress layer.
- Pass the exact JSON body as
payload; pass the provider delivery ID asidempotencyKeywhen available. - Configure stable
sourceNameandeventTypeidentifiers. - Map only the fields needed by later steps through
contactMapping. - Require
status: success, store or deduplicate byeventKey, and routeeventProposalto a separately approved ACL-aware persistence step if a durable business object is required.
Inputs
| Input | Required | Description |
|---|---|---|
payload | Yes | One non-null JSON document, capped at 256 KiB of UTF-8. |
idempotencyKey | No | Caller-owned provider delivery identifier, 1–128 bounded safe characters. |
occurredAt | No | RFC 3339 event time supplied by the trusted receiver. |
payload remains in memory only for parsing and hashing. It is not copied into eventProposal, error, or logs.
Outputs
| Output | Description |
|---|---|
status | success or error. |
eventKey | webhook_ plus a deterministic SHA-256 digest derived from the source, event type, and provider idempotency key or payload digest. |
payloadSha256 | SHA-256 digest of the accepted payload bytes. |
sourceName | Validated source-system identifier. |
eventType | Validated normalized event type. |
extractedFields | Only fields explicitly selected through contactMapping. |
eventProposal | Normalized proposal containing the event key, type, source, payload digest, optional occurrence time, and extracted data. |
persistencePerformed | Always false. |
error | Safe object with code, message, and retryable: false. |
The module never returns contactId or engagementEventId because it does not create those records.
IntegrationAccount Requirements
None. WebhookIngestModule performs no network call and must not receive provider tokens, signing secrets, passwords, or other credentials.
Authenticate and rate-limit the inbound request before the workflow executes. If signature verification is needed, bind the secret to the ingress component's IntegrationAccount; do not place it in this module's payload or mapping.
Configuration
| Configuration | Required | Constraint |
|---|---|---|
sourceName | Yes | Stable identifier using letters, digits, ., _, :, or -; maximum 128 characters. |
eventType | Yes | Stable identifier using the same character set; maximum 64 characters. |
contactMapping | No | Object containing at most 32 output-field to simple JSONPath mappings. Default: {}. |
Supported paths begin with $ and use field or numeric array segments, such as $.form.email or $.items[0].contact.email. Recursive descent, filters, scripts, wildcards, quoted bracket properties, and negative indexes are rejected. Each extracted value is capped at 16 KiB.
Legacy createContact: true is rejected. A merge strategy cannot authorize persistence, and no random fallback ID is generated.
Operations
The module has one operation: normalize one payload.
- Validate configuration and the payload byte limit.
- Parse exactly one non-null JSON document.
- Validate the optional idempotency key and event time.
- Resolve the bounded mapping allowlist.
- Hash the payload and derive
eventKey. - Return a side-effect-free proposal.
The operation is deterministic for the same configuration and inputs. When idempotencyKey is omitted, identical payload bytes produce the same key for the same source and event type.
Errors and Failure Modes
| Code | Meaning | Recovery |
|---|---|---|
INVALID_JSON | The payload is not valid JSON. | Send one valid JSON document. Parser context and payload bytes are not returned. |
VALIDATION_ERROR | A required identifier, payload bound, timestamp, mapping, or legacy persistence request is invalid. | Correct the named input. Use a separate generated ACL-aware service for persistence. |
EXECUTION_ERROR | An unexpected local normalization failure occurred. | Inspect sanitized application diagnostics; retry only after correcting the runtime issue. |
All errors are non-retryable at this module boundary. The trusted ingress layer owns provider acknowledgement and delivery retry policy.
Example
Configuration:
{
"sourceName": "webflow-main",
"eventType": "form_submit",
"contactMapping": {
"email": "$.form.email",
"firstName": "$.form.first_name"
}
}
Input:
{
"payload": "{\"form\":{\"email\":\"ada@example.com\",\"first_name\":\"Ada\"},\"provider_metadata\":{\"secret\":\"not-selected\"}}",
"idempotencyKey": "evt_provider_123",
"occurredAt": "2026-08-09T12:30:00Z"
}
Expected shape:
{
"status": "success",
"eventKey": "webhook_<sha256>",
"payloadSha256": "<sha256>",
"sourceName": "webflow-main",
"eventType": "form_submit",
"extractedFields": {
"email": "ada@example.com",
"firstName": "Ada"
},
"eventProposal": {
"eventKey": "webhook_<sha256>",
"type": "form_submit",
"channel": "webhook",
"sourceSystem": "webflow-main",
"payloadSha256": "<sha256>",
"occurredAt": "2026-08-09T12:30Z",
"extractedData": {
"email": "ada@example.com",
"firstName": "Ada"
}
},
"persistencePerformed": false
}
The unselected provider_metadata value is absent from every output.
Notes
- Pagination does not apply; one execution accepts one JSON document.
- The payload limit is 256 KiB, mappings are capped at 32, and each selected value is capped at 16 KiB.
- The deterministic key supports deduplication but does not prove provider authenticity. Verify signatures and replay windows before invoking the workflow.
- The module has no network access, provider API dependency, retry loop, destructive behavior, or persistence side effect.
persistencePerformed: falseis a contract, not a transient status. Use generated owner/ACL-enforcing services for Contact or EngagementEvent writes.- The focused tests validate bounds, mapping behavior, deterministic keys, raw-payload non-disclosure, failure cleanup, and metadata discovery. Provider ingress, signature verification, rate limiting, and durable persistence remain external boundaries.