fluyio.
Webhooks (próximamente)

Buenas prácticas de webhooks (borrador)

Borrador técnico en inglés sobre receptores de webhooks; funcionalidad todavía no operativa.

Borrador técnico en inglés · no operativo

Los webhooks salientes todavía no entregan eventos. El cuerpo en inglés que sigue conserva una propuesta técnica: no es un contrato publicado ni una guía para activar una función disponible. No implementes una integración de producción contra estos ejemplos. La API pública actual ofrece suscriptores; consulta la hoja de ruta.

The hard part of webhooks is not receiving them — it is running them reliably at 3 am while a burst of retries hits your endpoint. The checklist below is what we recommend building into your receiver from day one.

Idempotency

Delivery is at-least-once, so your endpoint may receive the same event.id more than once: because your 200 was lost in transit, because a delivery was retried manually, or because the network blipped during a retry.

Persist the event.id of every event you successfully process and skip duplicates.

import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL!);

async function processOnce(eventId: string, fn: () => Promise<void>) {
  // SET key value NX EX 172800 → only set if not exists, 48h TTL
  const claimed = await redis.set(`fluyio:evt:${eventId}`, '1', 'EX', 172800, 'NX');
  if (claimed !== 'OK') return; // already processed
  await fn();
}

await processOnce(event.id, () => handleEvent(event));

A 48-hour TTL comfortably covers the retry window.

Asynchronous processing

Acknowledge with 200 OK as fast as possible — within one or two seconds is ideal. The 10-second timeout is a ceiling, not a target. Push the actual work onto a background queue.

// Good: verify → enqueue → 200 OK
app.post('/webhooks/fluyio', express.raw({ type: 'application/json' }), async (req, res) => {
  try {
    verify(req.body, req.header('Fluyio-Signature')!, process.env.FLUYIO_WEBHOOK_SECRET!);
  } catch {
    return res.status(400).send('bad signature');
  }
  await queue.add('process-fluyio-event', { event: JSON.parse(req.body.toString('utf8')) });
  res.status(200).send('ok');
});

Workers pick the job up and do the heavy lifting. If a worker fails it can retry safely thanks to the idempotency check above.

Verify signatures every time

Non-negotiable. The algorithm, with code for Node, Python and PHP, is in verifying signatures. If verification fails, return 400 and stop. Never trust the body of an unverified request.

Network filtering

Fluyio does not publish a fixed egress IP range yet, so do not build your security on IP allowlists: rely on signature verification, which is stronger anyway. If your firewall requires an allowlist, ask [email protected] before delivery ships.

HTTPS is required

Endpoint URLs must use https:// with a valid certificate. The TLS handshake counts against the 10-second budget, so keep your TLS configuration lean.

Subscribe narrowly

Pick the specific event types you care about when registering the endpoint in the dashboard. Avoid * outside staging: every additional event type is more traffic and more chances for your handler to throw on a payload you do not care about.

Local development

You cannot expose localhost to the internet, so use a tunnel during development:

ngrok http 3000
# Forwarding  https://abcd-1234.ngrok-free.app → http://localhost:3000

Paste the HTTPS URL into the dashboard as the endpoint URL.

Ordering

Deliveries are not guaranteed to arrive in the order they were emitted: retries and parallel dispatch both reorder them. Use the created timestamp on the envelope, or an updated_at inside data.object, when reconstructing state:

async function handleSubscriberUpdated(event) {
  const incoming = event.data.object;
  const current = await db.getSubscriber(incoming.id);

  if (current && new Date(current.updated_at) > new Date(incoming.updated_at)) {
    return; // we already have a fresher version
  }
  await db.upsertSubscriber(incoming);
}

For events without a natural version field, store the highest created you have seen per resource id and drop older ones.

Monitoring

Track in your own stack:

  • Success rate — 2xx / total attempts per endpoint per hour.
  • Latency — p95 of handler duration. If it climbs past two or three seconds you are heading for the 10-second timeout.
  • Endpoint state — the dashboard shows when an endpoint flips to failing or disabled; treat that as a page.

Checklist

  • Endpoint is HTTPS with a valid certificate.
  • Signature verified with HMAC SHA-256 and a constant-time comparison.
  • Timestamp tolerance enforced (5 minutes).
  • Raw body preserved before any framework parses JSON.
  • Handler returns 2xx in under two seconds.
  • Heavy work runs in a background queue.
  • event.id persisted for idempotency, with a TTL of at least 48 hours.
  • Out-of-order events handled via created or updated_at.
  • Monitoring on success rate and latency.
  • Runbook in place for re-enabling a disabled endpoint and retrying missed deliveries from the dashboard.

See also

En esta página