Case: durable webhook collection
A SaaS platform receives webhooks from third-party providers (payments, CRM events, CI notifications). Providers retry on 5xx but deliver exactly once per attempt and keep no replay log — so losing a message is permanent. The platform forwards normalized events onto a Kafka topic consumed by many downstream services.
Requirements
- Every accepted webhook must survive a process crash
- A malformed webhook must not block the stream
- At-least-once delivery is acceptable; downstream deduplicates by webhook id
Architecture
┌──────────┐ POST ┌──────────┐ persist ┌─────┐ process ┌───────┐
│ Webhooks │────────▶│ HTTP │──────────▶│ WAL │────────────▶│ Kafka │
│ providers│ │ input │ first └─────┘ │ sink │
└──────────┘ └──────────┘ └───────┘
ack only after Kafka confirms
The load-bearing decision: HTTP is not replayable, so the WAL
(durability: block) sits between the input and the pipeline. The input is
acknowledged only after Kafka confirms the produce.
Configuration
Validated example: examples/case_webhook_durable.yaml
streams:
- id: webhooks-to-kafka
input:
type: http
address: "0.0.0.0:8080"
path: "/webhooks"
durability:
enabled: true
path: "./data/wal"
sync: "group_commit"
pipeline:
thread_num: 4
processors:
- type: json_to_arrow
- type: sql
query: "SELECT * FROM flow"
- type: arrow_to_json
output:
type: kafka
brokers:
- localhost:9092
topic:
type: value
value: webhooks.raw
client_id: arkflow-webhooks
error_output:
type: stdout
Run and expected outcome
./target/release/arkflow --config examples/case_webhook_durable.yaml --validate
./target/release/arkflow --config examples/case_webhook_durable.yaml
# simulate a provider
curl -X POST http://localhost:8080/webhooks \
-H 'Content-Type: application/json' \
-d '{"webhook_id": "wh-1", "event": "invoice.paid"}'
- The message appears on the Kafka topic
webhooks.raw. kill -9the engine, restart it: unacknowledged webhooks replay from the WAL and reach Kafka — nothing accepted is lost.- A non-JSON POST routes to the error output; the stream keeps serving.
Trade-offs and variations
sync: per_entryinstead ofgroup_committrades throughput for a narrower loss window (see WAL optimization).- Swap the SQL processor for VRL to redact fields before forwarding.
- Exactly-once on the Kafka side is available with transactional output — see exactly-once.