Webhook Trigger Node
The Webhook Trigger node lets an external system start a DataLug workflow by POSTing a JSON payload to a signed URL. It sits in the Source group in the component browser: no upstream input port, just an OUT port carrying the delivered payload as row data.
When to use
- An external system emits events (Stripe, Shopify, GitHub, a custom sender) and you want each event to trigger a DataLug run.
- You want an inbound webhook that authenticates with HMAC (Stripe-style header) so unsigned or replayed calls are rejected before touching the runtime.
- You need the delivery to be persisted regardless of runtime dispatch outcome so failed dispatches are diagnosable from the account webhooks UI.
Setup
- Create a webhook from Account -> Webhooks (Owner/Admin only).
- Copy the returned
secretimmediately -- DataLug shows it once and never again. Store it in your sender's config. - Drop a Webhook Trigger node onto your flow canvas and bind it to the webhook you just created.
- Point your sender at the inbound URL:
https://<your-datalug-host>/api/webhooks/<webhookId>.
Signature format
Each POST must include this header:
X-DataLug-Signature: t=<unix-seconds>,v1=<hex-hmac>
v1 is HMAC_SHA256(secret, "<t>." + raw-body-bytes). The raw body bytes matter -- do not JSON-reserialize the payload before hashing. Any downstream tool that supports Stripe-style signature headers can be pointed at DataLug with minor tweaks.
The t= timestamp is compared against server time with a 5-minute tolerance by default (operator override: DLR_WEBHOOK_HMAC_TOLERANCE_SECONDS). Timestamps outside the window are rejected as replays.
Delivery states
Every inbound POST creates a WebhookDelivery row -- even failed ones, so you can see rejection reasons in the management UI.
| Status | Meaning |
|---|---|
Accepted | HMAC verified, payload parsed as JSON. Awaiting runtime dispatch. |
Rejected | HMAC mismatch, missing/malformed signature, replay window elapsed, or unparseable JSON body. rejectionReason records which. |
Dispatched | Runtime accepted the run. |
Failed | Runtime rejected the run. |
Automatic runtime dispatch of accepted webhook deliveries: after a delivery is Accepted, DataLug will forward it to the runtime and transition the delivery to Dispatched (or Failed if the runtime rejects) without a manual retry.
Response codes
| Code | When |
|---|---|
202 Accepted | HMAC passed, delivery recorded, deliveryId returned. |
400 Bad Request | Payload was signed correctly but the body is not valid JSON. |
401 Unauthorized | Signature failed to verify (bad secret, tampered body, replay). |
404 Not Found | Unknown webhookId. Nothing is persisted. |
410 Gone | Webhook is disabled. Reactivate from the management UI. |
Rotating the secret
Rotate the secret whenever a sender's credentials might be exposed. Rotation is destructive to in-flight senders: the OLD secret stops verifying immediately. Plan a short cutover.
Rate limits
Two independent limiters run BEFORE the body parser so bogus traffic can't pump the delivery table:
| Env var | Default | Scope |
|---|---|---|
DLR_WEBHOOK_INBOUND_LIMIT_PER_WEBHOOK | 100 | Per :webhookId |
DLR_WEBHOOK_INBOUND_LIMIT_PER_ACCOUNT | 500 | Per account (sum across all its webhooks) |
DLR_WEBHOOK_INBOUND_LIMIT_WINDOW_MS | 60000 | Shared window |
Throttled requests return 429 with standard RateLimit-* + Retry-After headers so senders can back off.
Retry from the UI
Failed deliveries can be retried from Account -> Settings -> Webhook Triggers -> Deliveries. Only Failed deliveries are eligible - Rejected deliveries would just fail again (the HMAC would still be invalid), and Dispatched deliveries have already produced a run.
Retries re-drive the delivery through the same dispatcher the fresh inbound path uses. Successful retries transition the delivery to Dispatched with the new run id; failed retries stay Failed with the updated rejection reason.
Example flow fixture
See qa/fixtures/webhook-trigger-example-flow.json for a complete Webhook Trigger -> Transform -> Load reference chain with a sampleInboundPayload block that mirrors a real Stripe checkout.session.completed event.