WLI: Setting up the sample Backend Proxy
Updated today
🔗Backend communication
Configuring the backend proxy for your Metricool integration means configuring three things:
The credentials it forwards to Metricool on your behalf.
The HTTPS certificate the iframe integration requires.
The mapping between your own local routes and Metricool's real API paths.
This article walks through all three using the reference proxy built for this integration proyect (server_https.py).
Prerequisites for developing this project
Before configuring the proxy, have the following ready:
Your Metricool REST API Access token, from the Menu > Account Settings > API.
Administrator or root privileges on the machine that will run the proxy — we only accept the standard port 443 for the iframe integration, and binding to it requires elevated permissions.
A valid HTTPS certificate and private key for the domain your proxy will serve.
Python 3 with no extra dependencies — the reference proxy only uses the standard library (
http.server,urllib.request,ssl).
Setting your Metricool credentials
The proxy is the only place your userToken lives, so it must come from the environment the server runs in — never from a literal string in the file.
Get your token from Menu > Account Settings > API in Metricool.
Set it as an environment variable in the shell that will run the proxy:
$env:METRICOOL_USER_TOKEN = "your_token_here"Read it with
os.environ.getand attach it to every forwarded request as theX-Mc-Authheader:METRICOOL_USER_TOKEN = os.environ.get("METRICOOL_USER_TOKEN") headers = {"X-Mc-Auth": METRICOOL_USER_TOKEN}
Never leave a real token as a hardcoded fallback value in: os.environ.get(...).
Anyone with read access to the file gets full access to the connected account — set the variable in the environment and let the lookup fail loudly if it's missing.
Enabling HTTPS on port 443
The iframe integration only works over HTTPS, on the standard port 443, so your proxy needs a TLS context wrapped around its socket before it starts listening.
Obtain a certificate and private key for your domain (from your existing CA or a service like Let's Encrypt).
Reference both files in the proxy:
KEYFILE = "iframe_key.pem" CERTFILE = "iframe_cert.pem"Wrap the server socket with an SSL context before calling
serve_forever():context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) context.load_cert_chain(certfile=CERTFILE, keyfile=KEYFILE) httpd.socket = context.wrap_socket(httpd.socket, server_side=True)Run the proxy with elevated privileges (
sudoon Linux/macOS, an elevated terminal on Windows) — binding port 443 otherwise fails on most systems.
Mapping local routes to Metricool's API
Your frontend only ever calls routes on your own domain (see WLI: Integration Architecture); the proxy is what decides which real Metricool path each local route maps to.
We don't version every endpoint the same way, so the mapping needs an explicit case for the ones that are versioned and a generic fallback for the rest:
if path == "/api/scheduler/posts":
remote_path = "/v2/scheduler/posts"
elif path.startswith("/api/"):
remote_path = path[len("/api"):]The proxy then appends whatever query string your frontend sent and forwards the request as-is:
target_url = f"{METRICOOL_API_BASE}{remote_path}"
if parsed.query:
target_url = f"{target_url}?{parsed.query}"
request = urllib.request.Request(
target_url, method="GET",
headers={"X-Mc-Auth": METRICOOL_USER_TOKEN},
)The proxy does not add
userIdfor you — it only attaches the token. Your frontend is responsible for includinguserIdin the query string it calls the proxy with.The explicit branch handles endpoints like
scheduler/posts, which lives under/v2/on our side; the generic branch just strips the/apiprefix for everything else, like/api/admin/add-profile→/admin/add-profile. Thus Adding a new local route means adding one more branch here — the exact parameters each endpoint expects are documented separately, per endpoint (check the list here).Once the remote path is resolved, the proxy appends the original query string as-is and forwards the request, relaying back the real status code and
Content-Typefrom Metricool — error responses aren't guaranteed to be valid JSON, so forcing a content type here would only hide the real problem.
Additional Resources:
Additional Resources