Signatures both ways
Incoming events are verified with HMAC-SHA256 over timestamp.body, compared in constant time, rejected outside a five-minute window. Outgoing deliveries are signed the same way, so the receiving end can check us too.
Integration engine · running live
Anyone can call an API once. The hard part is the Tuesday the destination is down, the same order arrives twice, and someone has to prove what happened. Conduit takes an event on a signed endpoint, runs it through steps you can watch, retries what fails, and keeps the receipt.
—
—
Click a step to open it · the canvas follows the event as it runs
No runs yet.
Deliveries land here. The built-in inbox is a real destination — it stores exactly what was sent, with the signature header.
This endpoint is public. Point Stripe, Shopify, a form or your own script at it, or paste this into a terminal.
—
The signature header is t=<unix seconds>,v1=<HMAC-SHA256 of "t.body"> — the same shape Stripe uses. Turn the switch off above if you would rather send unsigned events while you experiment.
The whole flow is data. This is what the engine stores and runs.
—
How it works
Your endpoint checks the HMAC signature against the flow's secret, with a five-minute tolerance, then looks at the idempotency key. A repeat inside 24 hours returns the original run instead of doing the work twice.
Filter, reshape, branch, ask a small model to label it, call another API, wait, deliver. Every step records what it received, what it produced and how long it took.
A destination that answers 5xx, times out or refuses the connection is retried after 5, 12 and 40 seconds. Anything still failing is parked as a dead letter with the whole trace attached.
Open any run and read it end to end: the raw event, each step, the signed body that went out, the status that came back. Replay it against the current version of the flow with one click.
Reliability
These are the reasons integrations fall over in month three. They are built in here, not planned for later.
Incoming events are verified with HMAC-SHA256 over timestamp.body, compared in constant time, rejected outside a five-minute window. Outgoing deliveries are signed the same way, so the receiving end can check us too.
Senders retry. They always do. A repeated Idempotency-Key inside 24 hours returns the first run's result and does no work, so a customer is not charged, emailed or created twice.
Only the retryable failures — 5xx, 429, timeouts, connection errors — earn a retry. A 400 does not; retrying a malformed request forever is how queues die. Three attempts, then the dead-letter list.
A retry picks up at the step that failed with the payload it had, so the steps before it never run twice. That matters when step two already charged a card.
Steps that reach out are https only, and private, loopback and link-local addresses are refused before the request is made. A pipeline that fetches URLs is an SSRF hole unless somebody closes it.
Body size, step count, outbound calls per run, model calls per run and per day, events per flow. Every limit answers with a plain sentence rather than a stack trace.
A label is a pure function of the model, the label list and the text, so it is stored and reused: an event that has been seen before comes back in milliseconds and costs nothing. It is also the honest answer to the slowest thing here — a fresh model call on the free tier can take a few seconds, and nothing in this pipeline should pay that twice for the same input.
Built
One Worker holds the whole engine: the public endpoint, the runner, the retry scheduler and the API this page talks to. Flows, runs, traces and deliveries live in D1. The labelling step calls a small open-weight model on Workers AI — a couple of neurons per event, no external AI account.
Retries are picked up two ways: anything touching a flow drains the ones that are due, and a cron trigger every minute catches the rest. That is why a retry lands while you are still looking at the run.
A label is a pure function of the model, your label list and the text, so it is cached: the same event never pays for a second model call. That is also why the samples on this page come back in a few hundred milliseconds while a brand-new event waits on the model.
Measured on this deployment
For a client I would add: a queue in front of the runner, so a slow model call or a burst never holds the sender's connection, per-tenant keys and role-based access, a dead-letter view with bulk replay, alerting on the failure rate, and retention that matches whatever their auditors ask for.
Questions
It is a working demo, and it is honest about that. Flows you build live for seven days and are capped at a few hundred events. If you want this shape of thing in your business, the useful version is one built around your systems — that is the work I do.
Yes. Create a flow, copy the endpoint, and paste it into Stripe, Shopify, Typeform, GitHub or anything else that posts JSON. Turn the signature switch off if the sender signs with its own scheme, or leave it on and sign with the secret shown here.
Only the labelling step, and only from a fixed list of labels you write. It never invents a field, and if its answer is not one of your labels the step is marked unsure rather than guessing quietly. Everything else — filters, branches, mapping — is plain, deterministic code.
Into D1, on Cloudflare, in the flow you created, and out again to whatever destination you chose. Flows expire after seven days and take their runs and deliveries with them. Do not send anything you would not paste into a public demo.
Because the whole thing — endpoint, engine, scheduler, database and the small model — fits in one deployment with no server to run, and it costs nothing at this size. For a client with real volume I would make the same call or a different one depending on their stack; I would rather say that than pretend one answer fits everything.
Webhooks, syncs, legacy APIs and the plumbing between systems that were never meant to talk — that is most of what I do. Tell me what breaks and I will tell you what it takes.