Skip to content
Wix Engine
Velo Tutorials

How to Connect Wix to Any External API with Velo

Usama Mehmood, Co-owner & CEO at Wix EngineUsama MehmoodCo-owner & CEO · 9 min read

To connect a Wix site to an external API with Velo, you write a backend web module (a .web.js file) that calls the API with wix-fetch, reads the API key from the Wix Secrets Manager, and exports the function as a web method. Your page code then imports that function and calls it like any async function. The API key never reaches the browser, and the only thing the page can do is call the specific function you exposed. This tutorial builds a working example and covers the mistakes that cause most support requests we get.

Key takeaways

  • Make API calls from backend code, not page code. Backend calls avoid browser CORS restrictions and keep credentials private.
  • Store keys in the Secrets Manager and read them with wix-secrets-backend.v2 wrapped in elevate() from wix-auth. The old wix-secrets-backend getSecret() was deprecated in September 2025.
  • Export backend functions with webMethod() from wix-web-module and set the narrowest permission that works.
  • Validate inputs, check response.ok, return only the fields the page needs, and cache results that do not change often.
  • For APIs that need to call you (webhooks), use HTTP functions instead.

What you need

  • A Wix site with Dev Mode (Velo) turned on. Works on both Wix Studio and the Classic Editor.
  • An API to call. This tutorial uses a weather API because it is free and simple; the pattern is identical for a CRM, a payment provider or an AI model.
  • An API key for that service. Many APIs also work without a key for public data; the secrets part still applies the moment you have one.

If you have never written Velo code, start with our Wix Velo tutorial for beginners and come back. This post assumes you can find the code panel and add a backend file.

Why the call goes in the backend

You can call fetch from page code in Velo. For public GET requests to APIs that allow cross-origin access, it works. Two problems make it the wrong default:

  1. CORS. Browsers restrict cross-origin requests. Many APIs reject calls from a browser, and Wix's own documentation tells you to move those calls to the backend.
  2. Secrets. Anything in page code is visible to anyone who opens the browser's developer tools. An API key in page code is a public API key. We find this on sites we take over more often than you would expect, and it is one of the first things we check when we hire out a Wix developer to review an existing site.

Backend code runs on Wix's servers. The visitor's browser never sees it. That is where API calls belong.

Step 1: Store the API key in the Secrets Manager

In your site's dashboard, open Developer Tools → Secrets Manager and add a new secret. Give it a name you will use in code, such as openWeatherApiKey, and paste the key as the value.

Names are case-sensitive, and renaming or deleting a secret breaks every piece of code that uses it, so pick names once.

Step 2: Write the backend web module

In the code panel, under Backend, add a new web module. The file must end in .web.js. Call it weather.web.js.

// backend/weather.web.js
import { Permissions, webMethod } from 'wix-web-module';
import { fetch } from 'wix-fetch';
import { secrets } from 'wix-secrets-backend.v2';
import { elevate } from 'wix-auth';

// Reading a secret value needs elevated permissions in the v2 API
const getSecretValue = elevate(secrets.getSecretValue);

export const getWeather = webMethod(Permissions.Anyone, async (city) => {
  // 1. Validate what the page sent us. Never trust frontend input.
  if (typeof city !== 'string' || city.trim().length === 0 || city.length > 60) {
    throw new Error('Please enter a valid city name.');
  }

  // 2. Read the API key from the Secrets Manager
  const apiKey = await getSecretValue('openWeatherApiKey');

  // 3. Build the request
  const url =
    'https://api.openweathermap.org/data/2.5/weather' +
    `?q=${encodeURIComponent(city.trim())}&units=metric&appid=${apiKey}`;

  // 4. Call the API
  const response = await fetch(url, { method: 'get' });

  // 5. Handle failure explicitly. 4xx and 5xx do not throw on their own.
  if (!response.ok) {
    console.error(`Weather API error: ${response.status} ${response.statusText}`);
    throw new Error('Could not get the weather right now.');
  }

  const data = await response.json();

  // 6. Return only what the page needs, not the whole API response
  return {
    description: data.weather?.[0]?.description ?? 'unknown',
    temperature: data.main?.temp ?? null,
  };
});

Four things worth noticing:

  • webMethod(Permissions.Anyone, fn) is what makes the function callable from the page. The first argument is who may call it: Permissions.Anyone, Permissions.SiteMember or Permissions.Admin. Use the narrowest that works. A function that reads a member's own data should be SiteMember; a function that triggers an admin-only sync should be Admin.
  • elevate(secrets.getSecretValue) comes from Wix's own example for the v2 Secrets API. Without elevation, the call fails when triggered by a site visitor.
  • response.ok is the check you cannot skip. wix-fetch resolves on 2xx, 4xx and 5xx; only network failures reject.
  • We return two fields, not the full payload. Smaller responses are faster, and you are not leaking data the page has no business seeing.

Step 3: Call it from page code

Add a text input (#cityInput), a button (#getWeatherButton) and a text element (#resultText) to a page, then open the page's code:

// Page code
import { getWeather } from 'backend/weather.web';

$w.onReady(() => {
  $w('#getWeatherButton').onClick(async () => {
    const city = $w('#cityInput').value;
    $w('#resultText').text = 'Loading…';

    try {
      const weather = await getWeather(city);
      $w('#resultText').text = `${weather.description}, ${weather.temperature}°C`;
    } catch (err) {
      $w('#resultText').text = err.message;
    }
  });
});

Note the import path: backend/weather.web, without the .js. Web methods are always asynchronous, so you await them.

Publish or preview, type a city, click the button. If you see the weather, the whole chain works: page → web method → Secrets Manager → external API → back.

Step 4: A POST example (sending data to an API)

Reading data is half of integrations. The other half is sending data: a form submission to a CRM, an order to a fulfilment system, a message to an AI model. Same pattern, different method and headers:

// backend/crm.web.js
import { Permissions, webMethod } from 'wix-web-module';
import { fetch } from 'wix-fetch';
import { secrets } from 'wix-secrets-backend.v2';
import { elevate } from 'wix-auth';

const getSecretValue = elevate(secrets.getSecretValue);

export const createLead = webMethod(Permissions.Anyone, async (lead) => {
  const email = String(lead?.email ?? '').trim();
  const name = String(lead?.name ?? '').trim();
  if (!email.includes('@') || name.length === 0) {
    throw new Error('Name and a valid email are required.');
  }

  const token = await getSecretValue('crmApiToken');

  const response = await fetch('https://api.example-crm.com/v1/contacts', {
    method: 'post',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`,
    },
    body: JSON.stringify({ email, name, source: 'website' }),
  });

  if (!response.ok) {
    const text = await response.text();
    console.error(`CRM error ${response.status}: ${text}`);
    throw new Error('We could not save your details. Please try again.');
  }

  const created = await response.json();
  return { id: created.id };
});

Replace the URL, header format and body with whatever your API's documentation specifies. Most REST APIs use a Bearer token or an x-api-key header; some want form-encoded bodies (Content-Type: application/x-www-form-urlencoded). The Velo side does not change.

Common mistakes (and how to avoid them)

Calling the deprecated secrets API. Older tutorials import getSecret from wix-secrets-backend. Wix deprecated it in September 2025. Use secrets.getSecretValue from wix-secrets-backend.v2 with elevate() as shown above.

Exposing listSecretInfo to anyone. Wix's docs are explicit: do not call listSecretInfo in a .web.js file with Permissions.Anyone. If you need it, put it in a plain .js backend file or set the web method to Admin.

Forgetting that Permissions.Anyone means anyone. A web method with Anyone can be called by any visitor, repeatedly, including from scripts. If the function costs money per call (AI APIs, SMS gateways), add rate limiting (for example, a counter in a CMS collection keyed by IP or member ID), require SiteMember, or validate a reCAPTCHA token first.

Not handling errors. An unhandled throw in a web method shows up on the page as a generic failure. Throw clear messages for the user and log the real details with console.error so you can see them in the site monitoring logs.

Returning the raw API response. Third-party responses often include fields you did not ask for. Pick what the page needs.

Making the same call on every page load. If the data changes hourly, do not call the API 10,000 times a day. Cache it: write the result to a CMS collection with a timestamp and serve it from there until it is stale, or use a scheduled job (jobs.config in the backend) to refresh it on a timer.

HTTP instead of HTTPS. Wix sites are HTTPS-only, and wix-fetch will not call an http:// URL.

When the API needs to call you: HTTP functions

Everything above is your site calling out. Webhooks go the other way: Stripe, a CRM or a form tool wants to send data to your Wix site. For that, Velo has HTTP functions. Create backend/http-functions.js and export functions named get_, post_, put_ or delete_ followed by the endpoint name. A post_stripe function becomes available at https://yoursite.com/_functions/stripe. Verify the webhook signature, do the work, and return a response. That is a tutorial of its own; the key point is that inbound and outbound integrations use different Velo tools.

Beyond fetch: npm packages and AI APIs

The Wix code panel can install npm packages, so if an API publishes an official JavaScript client (SendGrid, Stripe, the OpenAI and Anthropic SDKs), you can use it inside a backend file with the same web-method wrapper and the same secrets handling. Wix's own docs show SendGrid used this way.

AI APIs are just another POST request with a Bearer token, which is why adding AI features to Wix is more accessible than people expect; we showed one route in how to add an AI chatbot to a Wix website, and our AI integration service handles the production version with streaming, retries and cost controls.

If you find yourself writing a lot of API code and wishing you had a full front-end framework too, that is the point where Wix Headless starts to make sense. For most sites, a few well-written web modules are all you need.

How Wix Engine can help

We have connected Wix sites to CRMs, payment providers, shipping systems, ERPs, Google services and AI models for clients in 15+ countries, always from backend code with keys in the Secrets Manager. If you need an integration built properly, or you have inherited one that leaks keys or fails silently, our Wix Velo development team can build, fix and document it.

Book a Free Call

FAQ

Can Wix connect to external APIs?

Yes. Wix's Velo platform includes wix-fetch for calling any HTTPS REST API from backend code, the Secrets Manager for storing API keys, web modules for exposing backend functions to page code, HTTP functions for receiving webhooks, and npm package support for official API clients. This works on both Wix Studio and the Classic Editor.

Should I use wix-fetch in the frontend or the backend?

Backend, almost always. Page code is visible to visitors, so any key in it is public, and browsers block many cross-origin requests. Put the fetch call in a .web.js backend file, export it with webMethod(), and call that from the page. Frontend fetch is only reasonable for public, keyless GET requests to APIs that allow it.

How do I store API keys in Wix?

Use the Secrets Manager under Developer Tools in the site dashboard. In backend code, import secrets from wix-secrets-backend.v2, wrap secrets.getSecretValue with elevate() from wix-auth, and call it with the secret's name. Never hardcode keys and never return a secret's value to the frontend.

Why does my Velo fetch call return a CORS error?

Because the call is running in the browser (page code) and the API does not allow cross-origin requests from your domain. Move the fetch call into a backend web module and call that from the page instead. Backend requests are server-to-server and are not subject to browser CORS rules.

Is the old getSecret() function still working?

Wix deprecated getSecret() and the rest of the original wix-secrets-backend module in September 2025 in favor of wix-secrets-backend.v2. Existing code may keep running for a while, but new code should use secrets.getSecretValue() with elevate(), and old code should be updated before it stops working.

Usama Mehmood, Co-owner & CEO at Wix Engine
Written by

Usama Mehmood

Co-owner & CEO at Wix Engine

Wix Certified designer and developer, senior software and AI engineer with 15 years of experience. Started Wix Engine in 2014.

LinkedIn ↗

Want an expert to look at your project?

Book a free 30-minute call with the Wix Engine team. You'll get honest advice and a detailed proposal within 24 hours.

WhatsAppBook a Free Call

Book a free 30-minute call