Every SaaS product hits the same wall:
- "We just need to POST to this webhook."
- "We just need to push new customers into our CRM."
- "We just need to notify a partner when an event happens."
So you add a couple of HTTP calls, maybe a job queue, and it works… until it doesn't.
The slow death by one-off integrations
A familiar progression:
1. Inline HTTP calls
You fire off requests to HubSpot, Salesforce, or a webhook inside your main request cycle.
- If the call is slow, your API is slow.
- If the call fails, you have to decide: retry? swallow? error?
2. Background jobs and queues
You add a queue (Bull, Sidekiq, SQS, etc.) and fork work into workers.
- Better latency, but now you own:
- retry policies
- backoff
- dead-letter queues
- poison messages
If you've ever implemented webhook retry logic with exponential backoff, you know this isn't a one-afternoon task—it's an ongoing maintenance surface.
3. Each integration becomes its own mini-platform
Different auth, payloads, rate limits, and error semantics mean:
- per-integration code paths
- per-integration retry quirks
- per-integration dashboards or logs
Over time you're not just maintaining your product—you're maintaining a one-off integration platform.
The build-vs-buy math
You can absolutely build webhook infrastructure yourself. Most teams estimate two to three weeks for the initial implementation. What they don't estimate is the two to three hours per week maintaining it for the next two years: debugging silent failures, adding retry logic for a new destination that behaves differently, building a dashboard so someone can answer "why didn't this webhook fire?"
That maintenance cost is the real expense—not the initial build.
What you really need: a dedicated integration layer
Step back and most integrations follow the same pattern:
1. Something happens in your app
e.g. lead.created, subscription.renewed, invoice.paid.
2. You emit an event with a clean payload.
3. Routing & fan-out decide:
- which destinations should receive it
- how the payload should be mapped
- when conditions should block or alter behavior
This is where event routing becomes critical—especially when one event needs to reach multiple destinations with different payload shapes.
4. Delivery engine handles:
- queues & parallel fan-out
- retries & backoff
- rate limiting
- dead letters & observability
That's what a universal integration layer (like Meshes) is built to do.
Instead of sprinkling webhook calls and queue logic all over your codebase, you:
- define event types (e.g.
lead.created) - configure rules and connections
- send events to one API, and let the integration layer handle the rest
A simple example: turning one event into many actions
Let's say your app wants to:
- send a new lead to HubSpot
- notify your backend via webhook
- add the lead to a Mailchimp list
With an integration layer, your app doesn't know about three different APIs. It only knows how to emit an event:
// backend (Node/TS) – pseudo example
import fetch from 'node-fetch';
import { createMeshesMachineToken } from './meshes-auth';
async function onLeadCreated(lead: {
id: string;
email: string;
firstName?: string;
lastName?: string;
source: string;
}) {
const jwtToken = await createMeshesMachineToken();
await fetch('https://api.meshes.io/api/v1/events', {
method: 'POST',
headers: {
Authorization: `Bearer ${jwtToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
workspace: 'ws_01...',
event: 'lead.created',
payload: lead,
}),
});
}
Rules + connections decide:
- which CRMs/webhooks get the event
- how the payload is transformed per destination
- when to retry or give up
Why this matters for developers
A dedicated integration layer gives you:
- One place for retries, backoff, and dead letters No more re-implementing retry logic per service.
- One schema per event, many projections Map the same
lead.createdpayload into HubSpot, Salesforce, or webhooks without cluttering your app. - Multi-tenant isolation Each customer / environment can have its own workspaces, connections, and rules without leaking credentials.
- Better observability You get event-level and destination-level logs: what failed, why, and what was retried.
When is it time to stop hand-rolling?
A few signals:
- You've built more than 2–3 external integrations.
- You're debugging "why didn't this webhook fire?" more than you'd like.
- You have multiple queues or worker pools doing similar things.
- Product wants to offer "integrations as a feature" to customers.
At that point, investing in a universal layer is cheaper than continuing to bolt one-offs onto your core app. The teams who keep building in-house don't regret the initial build—they regret the years of upkeep that follow.
If you'd rather not build that in-house, that's the whole idea behind Meshes:
"Emit events, define rules, plug in destinations. The platform handles fan-out, retries, and delivery."
You keep your app simple—and still ship integrations fast.
Ready to stop maintaining webhook infrastructure? Join Meshes and let the platform handle delivery while you ship features.
