Render · Hosting, Workflows, Cron
Sellers who never ship, buyers who go quiet, holds that get old: real deals stall. Handshake runs on Render, and a Render Workflow enforces every deadline automatically, even when nobody has the page open.
How a deadline gets enforced
Render Cron
every 5 min
sweepDeals
Workflow task
enforceDeadline × N
one run per deal
Handshake app
re-checks, then acts
PayPal
void · capture · renew
Real run · PayPal sandbox · deadlines sped up for the test
The deadlines
Four timers run on every protected deal. Both people see a live countdown, and the workflow acts the moment one runs out.
- 01
Ship within 3 days
The seller has three days from payment to ship. If they don't, the PayPal hold is voided and the buyer pays nothing.
Protects the buyer - 02
Renew the hold on day 3
PayPal guarantees a hold for three days. Handshake reauthorizes it then, so the money stays reserved while the item travels.
PayPal reauthorize - 03
Respond within 7 days of shipping
If the buyer neither confirms nor reports a problem, payment is captured and the seller is paid, but only when the seller's label photo matched the buyer's PayPal address. A typed tracking number never triggers it.
Protects the seller - 04
Settle claims within 3 days
If nobody declines a damage proposal, the code-computed refund settles automatically. A decline sends it to PayPal instead.
Protects both
Built to move money safely
Background jobs that move money have one job: never do it twice. Every layer is built around that.
The app re-checks every time
The workflow decides when to act. The app checks the deadline again inside the deal's lock, so a retry after a success does nothing.
Retries only when they help
Render retries a failed run with backoff. Only transient failures (a PayPal 5xx, a deploy in progress) throw; a bad request is reported, not retried.
One sweep per tick
Each cron tick starts the sweep with an idempotency key, so a retried tick can't start a second one.
A secret nobody types
The app and the workflow share a secret that Render generates in a Blueprint environment group. It never appears in code or chat.
Settlement resumes
Capture, tracking and payout are recorded step by step. If the payout fails, the retry pays the seller without capturing twice.
Demo speed, same rules
One setting shrinks days to minutes for demos. The order of events and every safety check stay exactly the same.
The whole stack
Everything is defined in a single render.yaml, so the whole system deploys from one file.
- 01
Web service
The Next.js app and its API: deal pages, PayPal calls, AI checks. On the Starter plan, so it never sleeps and a judge's first click is instant.
Starter - 02
Persistent disk
Deals live on a 1 GB disk, written atomically, so they survive redeploys and restarts.
1 GB disk - 03
Tests gate every deploy
Render runs the money-rule tests before each build. If pricing, refund caps, negotiation limits or timers break, the deploy stops and the live site keeps working.
npm test - 04
Workflow + cron
A Render Workflow with two tasks enforces deadlines, started every five minutes by a Render cron job.
Workflows
Proof
3
timer actions run against PayPal's sandbox: a void, a capture with payout, and a rescheduled renewal.
End to end
0
duplicate money moves, across repeated sweeps and three simultaneous runs of the same deadline.
Idempotency
327
automated tests: 319 for the app, run on both the file store and Postgres before every deploy, and 8 for the workflow. Plus 44 live scenarios on the sandbox.
Vitest + live runs
What we use
sweepDeals task
task({ name: 'sweepDeals' })Asks the app which deals are past a deadline and fans out one run per deal with ctx.run.
enforceDeadline task
retry: { maxRetries: 4, backoffScaling: 2 }Each deadline runs on its own instance, retried with exponential backoff on transient failures.
Starting runs
render.workflows.startTask()The cron job starts each sweep with an idempotency key, so a retried tick can't double up.
Cron job
schedule: "*/5 * * * *"Render Workflows don't schedule themselves yet, so a cron job starts the sweep every five minutes.
Blueprint env group
generateValue: trueRender generates the shared secret the app uses to trust the workflow.
Persistent disk
disk: mountPath /var/dataDeals and their clocks survive deploys, so no deadline is ever lost.
Docs & source
- Workflow taskssweepDeals and enforceDeadline, with their retry settings.Open
- Timer engineWhat each deadline does, and how it stays safe to retry.Open
- Deadline rulesThe four timers, as one pure function shared by the server and the UI.Open
- Timer testsExactly-once, rescheduling and retry behavior, written down as tests.Open
- render.yamlThe whole system in one Blueprint.Open
- Render WorkflowsTasks, chained runs and retries.Open
- Workflows SDK for TypeScripttask(), ctx.run() and startTask().Open
- Cron jobsThe scheduler that starts each sweep.Open
- Blueprint referenceServices, disks and environment groups as code.Open