Add Distributed Locks to Your API in 5 Minutes
Two API servers receive the same webhook simultaneously. Both process the payment. Customer is charged twice.
Or two deploy scripts run concurrently. Both try to migrate the database. One wins. One corrupts the migration state.
You need distributed locking. Ensure only one process does the work.
The 5-Minute Version
Latch provides distributed locks with FIFO fairness and automatic TTL-based release.
Step 1: Acquire a lock
curl -X POST "https://api.solenoid.systems/v1/latch/deploy:prod/acquire" \
-H "Authorization: Bearer sm_your_api_key" \
-H "Content-Type: application/json" \
-d '{"ttl": 10000, "timeout": 10000}'
Response:
{
"status": "acquired",
"token": "550e8400-e29b-41d4-a716-446655440000",
"expires_at": 1706123466,
"key": "deploy:prod"
}
You have the lock. The token proves it.
Step 2: Do your work
While you hold the lock, no other client can acquire it. They wait in a FIFO queue.
Step 3: Release the lock
curl -X POST "https://api.solenoid.systems/v1/latch/deploy:prod/release" \
-H "Authorization: Bearer sm_your_api_key" \
-H "Content-Type: application/json" \
-d '{"token": "550e8400-e29b-41d4-a716-446655440000"}'
{ "status": "released" }
Next waiter in the queue gets the lock automatically.
Handling the Edge Cases
Auto-release on crash: Your process crashes without releasing the lock. The TTL expires. Latch releases automatically. Next waiter gets it.
No manual intervention. No leaked locks blocking forever.
Reentrant locking: Already hold the lock and need to extend it?
let token = null
// First acquire
const res1 = await fetch(url + '/acquire', {
method: 'POST',
headers: { 'Authorization': `Bearer ${key}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ ttl: 10000, timeout: 10000 })
})
token = (await res1.json()).token
// Work takes longer than expected - refresh TTL
const res2 = await fetch(url + '/acquire', {
method: 'POST',
headers: { 'Authorization': `Bearer ${key}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ ttl: 10000, token })
})
const result = await res2.json()
// result.reentrant === true
// TTL refreshed, same token returned
FIFO fairness: Locks are granted in request order. No starvation.
T=0s: Client A acquires lock
T=1s: Client B requests (waits)
T=2s: Client C requests (waits)
T=5s: Client A releases → B gets lock immediately
T=8s: Client B releases → C gets lock immediately
Client C never jumps ahead of Client B.
Real-World Patterns
Prevent duplicate webhook processing:
app.post('/webhooks/stripe', async (req, res) => {
const eventId = req.body.id
// Try to acquire lock for this event
const lockRes = await fetch(
`https://api.solenoid.systems/v1/latch/webhook:${eventId}/acquire`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.SOLENOID_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ ttl: 30000, timeout: 1000 })
}
)
if (lockRes.status === 408) {
// Another server is already processing this webhook
return res.json({ received: true })
}
const { token } = await lockRes.json()
try {
// Process webhook
await processStripeEvent(req.body)
// Release lock
await fetch(
`https://api.solenoid.systems/v1/latch/webhook:${eventId}/release`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.SOLENOID_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ token })
}
)
return res.json({ received: true })
} catch (error) {
// Lock auto-releases after TTL even if we crash here
throw error
}
})
If Stripe retries a webhook to multiple servers, only one processes it.
Deployment coordination:
#!/bin/bash
# deploy.sh
# Acquire lock (blocks until available)
LOCK_RESPONSE=$(curl -X POST "https://api.solenoid.systems/v1/latch/deploy:production/acquire" \
-H "Authorization: Bearer $SOLENOID_API_KEY" \
-H "Content-Type: application/json" \
-d '{"ttl": 300000, "timeout": 300000}')
TOKEN=$(echo $LOCK_RESPONSE | jq -r '.token')
if [ -z "$TOKEN" ]; then
echo "Failed to acquire deployment lock"
exit 1
fi
# Run deployment (exclusive access guaranteed)
echo "Running database migrations..."
npm run migrate
echo "Deploying services..."
npm run deploy
# Release lock
curl -X POST "https://api.solenoid.systems/v1/latch/deploy:production/release" \
-H "Authorization: Bearer $SOLENOID_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"token\": \"$TOKEN\"}"
echo "Deployment complete"
Multiple engineers can run this simultaneously. The second one waits for the first to finish.
Scheduled job coordination:
// Cron runs on multiple servers - only one should execute
async function runDailyReport() {
let token = null
try {
const res = await fetch(
'https://api.solenoid.systems/v1/latch/cron:daily-report/acquire',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.SOLENOID_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ ttl: 60000, timeout: 1000 })
}
)
if (res.status === 408) {
// Another server is running the job
console.log('Job already running on another server')
return
}
const data = await res.json()
token = data.token
// Run the job
await generateDailyReport()
await sendReportEmail()
} finally {
if (token) {
await fetch(
'https://api.solenoid.systems/v1/latch/cron:daily-report/release',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.SOLENOID_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ token })
}
)
}
}
}
Why Not Build This Yourself?
You could use Redis with SET NX EX. Set a key if it doesn’t exist, with expiration.
But that’s advisory locking. Race conditions exist between checking and setting. You need Lua scripts for atomicity. You need to handle Redis connection failures. You need to monitor for leaked locks.
Or you use Postgres SELECT FOR UPDATE. But now every lock check hits your primary database. Lock contention becomes database load.
Latch uses Cloudflare Durable Objects. Strongly consistent single-threaded execution per key. Locks are truly exclusive. Fairness is guaranteed by request order. Auto-release is built in.
You acquire locks. Cloudflare ensures mutual exclusion.
Try It Now
Free tier: 10,000 calls to prototype. Pro tier ($49.99/mo) and above: unlimited Latch usage.
# Acquire a lock
curl -X POST "https://api.solenoid.systems/v1/latch/test-lock/acquire" \
-H "Authorization: Bearer sm_your_api_key" \
-H "Content-Type: application/json" \
-d '{"ttl": 10000, "timeout": 10000}'
# Check status (doesn't cost credits)
curl "https://api.solenoid.systems/v1/latch/test-lock" \
-H "Authorization: Bearer sm_your_api_key"
# Release with the token from acquire response
curl -X POST "https://api.solenoid.systems/v1/latch/test-lock/release" \
-H "Authorization: Bearer sm_your_api_key" \
-H "Content-Type: application/json" \
-d '{"token": "YOUR_TOKEN_HERE"}'
Open two terminal windows. Acquire in both simultaneously. The second one waits for the first to release.
Your distributed locking, in three API calls.