Make (formerly Integromat) is great at stitching APIs together. The trouble starts when you try to involve a browser.
A lot of developers assume this flow will work:
- frontend app calls API directly
- Make scenario orchestrates some backend steps
- browser reads the response and moves on
Then CORS shows up and ruins the afternoon.
The core problem: Make runs server-to-server just fine, but browsers enforce CORS and Make does not magically bypass that for your frontend. If your app calls an API from the browser, the API still needs the right CORS headers. If your app calls a Make webhook from the browser, that webhook also needs to behave in a browser-friendly way.
Here’s the practical comparison guide I wish more teams read before building a Make-based integration.
The 4 common integration patterns
1. Browser → Third-party API directly
This is the cleanest setup when it works.
Your frontend sends fetch() requests straight to the vendor API. No Make in the request path. Make might still run separately for background automation.
Pros
- Lowest latency
- Simple architecture
- No extra relay layer to maintain
- Browser gets the response directly
Cons
- Vendor API must support CORS
- Browser-safe auth only; no secret API keys
- Preflight requests can break things
- Rate limits hit from end users directly
This works well for public APIs or OAuth-based APIs designed for SPAs.
GitHub is a good real-world example of a browser-friendly API. Its response includes:
access-control-allow-origin: *
access-control-expose-headers: ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset, Warning
That access-control-allow-origin: * means browsers can read the response from any origin, assuming credentials aren’t involved. The exposed headers are also a nice touch. If you want your frontend to read Link for pagination or X-RateLimit-Remaining, those headers must be explicitly exposed.
Example:
const res = await fetch('https://api.github.com/repos/octocat/Hello-World');
const data = await res.json();
console.log(res.headers.get('ETag'));
console.log(res.headers.get('X-RateLimit-Remaining'));
console.log(data.full_name);
Without access-control-expose-headers, some of those headers would be invisible to browser JavaScript even though they exist on the network response.
2. Browser → Make webhook → Third-party API
This is the pattern people reach for when the target API has bad CORS or needs secret credentials.
The idea is simple:
- browser sends request to Make webhook
- Make scenario calls the third-party API server-side
- Make returns the result
This can work, but there are tradeoffs.
Pros
- Hides API secrets from the browser
- Avoids third-party CORS limitations
- Lets you normalize ugly APIs
- Easy to add logging, branching, retries
Cons
- Added latency
- More moving parts
- Webhook responses are not a full replacement for a custom backend
- OPTIONS/preflight handling may be awkward depending on the webhook setup
- Debugging browser CORS against middleware is annoying
The biggest gotcha: your browser is no longer talking to the vendor API. It’s talking to Make. So the Make endpoint must satisfy browser CORS rules.
If your frontend sends JSON, custom headers, or credentials, the browser may send a preflight:
OPTIONS /webhook/abc123
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type, authorization
If the Make webhook doesn’t answer that preflight correctly, the browser blocks the real request before your scenario even runs.
Typical browser code:
await fetch('https://hook.make.com/your-webhook-id', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: '[email protected]',
event: 'signup'
})
});
That innocent Content-Type: application/json is enough to trigger preflight in many cases.
My opinion: this pattern is fine for low-volume internal tools, prototypes, and lightweight public forms. I don’t love it for core product flows where you need tight control over headers, auth, and response behavior.
3. Browser → Your backend → Make and other APIs
This is the boring architecture, which is why it’s usually the right one.
Your backend becomes the browser-facing API. It handles auth, CORS, validation, caching, and policy. It can trigger Make when automation is useful, but Make is not your public API surface.
Pros
- Full control over CORS
- Better security model
- Easier auth and session handling
- More predictable response contracts
- Better observability and error handling
Cons
- You have to build and maintain a backend
- More engineering work up front
- Another deployable component
If I’m building something customer-facing, this is the route I trust most.
Minimal Express example:
import express from 'express';
import cors from 'cors';
import fetch from 'node-fetch';
const app = express();
app.use(express.json());
app.use(cors({
origin: 'https://app.example.com',
methods: ['GET', 'POST', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
exposedHeaders: ['ETag', 'Link'],
credentials: true
}));
app.post('/api/enrich', async (req, res) => {
const makeRes = await fetch('https://hook.make.com/your-webhook-id', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(req.body)
});
const text = await makeRes.text();
res.set('Cache-Control', 'no-store');
res.send(text);
});
app.listen(3000);
Now you own the CORS policy instead of hoping every downstream service behaves nicely.
4. Make as background automation only
This is the least painful option.
Your app talks to your backend or directly to browser-friendly APIs. Make handles:
- syncing CRM records
- sending Slack alerts
- enriching data after the fact
- moving files between systems
- scheduled jobs
Pros
- No browser CORS dependency on Make
- Cleaner separation of concerns
- Better reliability for user-facing flows
Cons
- Not suitable if the browser needs immediate direct response from Make
- Requires async thinking and event-driven design
Honestly, this is where Make shines. I like Make far more as an automation engine than as a browser-facing middleware layer.
CORS-specific comparison
Direct API calls
Best when the API already supports browser use.
Look for:
access-control-allow-originaccess-control-allow-methodsaccess-control-allow-headersaccess-control-expose-headers
And if cookies or auth sessions are involved:
- no wildcard origin with credentials
access-control-allow-credentials: true
Make webhook relay
Best when you need secret handling and light orchestration, but only if the webhook plays nicely with browser requests.
Look for:
- preflight support for
OPTIONS - consistent response headers
- no assumptions about opaque redirects or non-JSON responses
Custom backend
Best when the frontend matters and you need predictable behavior.
You can also combine this with broader header hardening. If you’re cleaning up your edge layer anyway, it’s worth reviewing CSP and related headers too. csp-guide.com is useful for that side of the stack.
Common failures with Make integrations
“It works in Postman”
Of course it does. Postman doesn’t enforce browser CORS.
“The webhook returns 200 but fetch still fails”
That usually means the browser blocked access to the response because the CORS headers were missing or wrong.
“I can see the header in DevTools but JS can’t read it”
Then it probably wasn’t listed in Access-Control-Expose-Headers.
“We added * and it still fails”
Wildcard origin does not work with credentialed requests. If you send cookies or credentials: 'include', you need a specific origin.
How I test this stuff
I test three things separately:
- The actual browser request
- The preflight
OPTIONS - Which response headers are readable in JavaScript
A simple curl preflight check:
curl -i -X OPTIONS 'https://example.com/api' \
-H 'Origin: https://app.example.com' \
-H 'Access-Control-Request-Method: POST' \
-H 'Access-Control-Request-Headers: content-type, authorization'
If you want a quicker sanity check on header behavior, I’d use HeaderTest. It’s handy when you want to verify what a response is actually returning before blaming your frontend code.
My recommendation
If you’re choosing between these patterns for Make integrations:
- Use direct browser calls when the API is intentionally browser-friendly.
- Use Make as a relay only for simple cases where a little extra latency and CORS friction are acceptable.
- Use your own backend for serious product flows, authenticated apps, and anything customer-facing.
- Use Make in the background whenever possible. That’s where it earns its keep.
The mistake I see most often is treating Make like a drop-in replacement for an application backend. It isn’t. It’s excellent at automation. It’s mediocre as your public browser API boundary.
If your integration starts in the browser, design for browser rules first. CORS doesn’t care how elegant your Make scenario looks.