/v1/announcements/changes endpoint as the serverless polling fallback when a persistent SSE connection is not available.
Supabase is a natural home for a fintech product that needs authentication, Postgres, Row Level Security, and lightweight server-side functions. FXMacroData fits into that stack as the macro data layer: REST for canonical records, webhooks for release callbacks, the changes endpoint for cursor-based polling, and SSE streams for always-on listeners.
The important design choice is where each job runs. Supabase Edge Functions are Deno-based HTTP handlers, which makes them a strong fit for webhook receivers, authenticated REST proxies, and scheduled polling jobs. They are not the right place to hold an indefinite stream open. If your product needs an SSE listener, run that listener in an always-on worker and write the resulting events into Supabase.
Use FXMacroData webhooks to update Supabase only when a matching macro release is processed.
Schedule an Edge Function that polls /v1/announcements/changes with a cursor.
Run SSE in Railway, Fly.io, a VPS, or another always-on worker, then write events to Supabase.
1. Choose the Right Delivery Pattern
Do not make every user, page, or cron job poll every FXMacroData endpoint. Put one server-side integration behind your product, persist the release state you need, and let your app read from Supabase.
| Pattern | Use it for | Supabase fit |
|---|---|---|
| REST latest | Initial app state, cache repair, and daily bootstrap. | Good for a short Edge Function job. |
| Webhooks | Release-driven updates with signed HTTPS callbacks. | Best fit for Edge Functions. |
| Changes endpoint | Cursor-based polling when inbound webhooks are not suitable. | Good for Supabase Cron plus an Edge Function. |
| SSE stream | Live release listeners that can reconnect and stay warm. | Run outside Edge Functions, then write to Supabase. |
2. Create the Supabase Tables
Start with a durable event table and a tiny state table for the changes cursor. The event table gives you idempotency, auditability, and a single place for your dashboard or alerting layer to read recent release state.
create table public.fxmd_macro_events (
event_id text primary key,
source text not null,
currency text not null,
indicator text not null,
announcement_at timestamptz,
payload jsonb not null,
received_at timestamptz not null default now()
);
create table public.fxmd_integration_state (
name text primary key,
cursor text,
updated_at timestamptz not null default now()
);
If customers can see FXMacroData-powered output inside your product, review the Commercial Redistribution path as well. The application architecture can still use Supabase, but the commercial right is different when macro data is visible outside your own team.
3. Store Secrets Safely
Use Supabase Edge Function secrets for FXMacroData credentials and webhook signing secrets. Do not expose your FXMacroData API key in browser code, mobile clients, or customer-accessible database rows.
supabase secrets set FXMD_API_KEY=YOUR_API_KEY
supabase secrets set FXMD_WEBHOOK_SECRET=whsec_YOUR_SIGNING_SECRET
supabase secrets set SUPABASE_SERVICE_ROLE_KEY=YOUR_SERVICE_ROLE_KEY
Inside an Edge Function, read secrets with Deno.env.get("FXMD_API_KEY"). Supabase documents this as the standard way to access function secrets from Deno.
4. Bootstrap Current Macro State
Use /v1/announcements/{currency}/latest to seed the current state for each currency you support. This is a bootstrap and repair job, not something each customer session should run independently.
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
Deno.serve(async () => {
const db = createClient(Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!);
const apiKey = Deno.env.get("FXMD_API_KEY")!;
const currencies = ["usd", "eur", "gbp", "jpy", "aud", "cad", "chf", "nzd"];
for (const currency of currencies) {
const url = `https://api.fxmacrodata.com/v1/announcements/${currency}/latest?api_key=${apiKey}`;
const latest = await (await fetch(url)).json();
await db.from("fxmd_macro_events").upsert({
event_id: `${currency}_latest_bootstrap`,
source: "latest",
currency,
indicator: "latest",
payload: latest
});
}
return Response.json({ ok: true, currencies: currencies.length });
});
For historical charts, fetch only the indicators you actually display and cache the result. Avoid rebuilding full history every night unless your product really needs a rolling historical archive.
5. Receive FXMacroData Webhooks
Webhooks are the cleanest Supabase Edge Function integration. FXMacroData calls your public HTTPS function when a matching announcement is processed. Your function verifies the signature, stores the event, and returns quickly.
Create a subscription with a destination such as https://PROJECT_REF.functions.supabase.co/fxmd-webhook:
curl -X POST "https://api.fxmacrodata.com/v1/webhooks/subscriptions?api_key=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://PROJECT_REF.functions.supabase.co/fxmd-webhook",
"events": ["announcement"],
"currencies": ["usd", "eur"],
"indicators": ["inflation", "policy_rate"],
"description": "Supabase macro release receiver"
}'
Use the returned signing_secret as FXMD_WEBHOOK_SECRET. The helper below reconstructs the FXMacroData signature input from the timestamp, event ID, and raw request body.
async function hmacHex(secret: string, message: string) {
const enc = new TextEncoder();
const key = await crypto.subtle.importKey(
"raw", enc.encode(secret), { name: "HMAC", hash: "SHA-256" },
false, ["sign"]
);
const digest = await crypto.subtle.sign("HMAC", key, enc.encode(message));
return [...new Uint8Array(digest)]
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
}
function safeEqual(a: string, b: string) {
const aa = new TextEncoder().encode(a);
const bb = new TextEncoder().encode(b);
let diff = aa.length ^ bb.length;
for (let i = 0; i < Math.max(aa.length, bb.length); i++) {
diff |= (aa[i] ?? 0) ^ (bb[i] ?? 0);
}
return diff === 0;
}
async function verifyFxmd(req: Request, body: string) {
const ts = req.headers.get("x-fxmd-timestamp") ?? "";
const eventId = req.headers.get("x-fxmd-event-id") ?? "";
const supplied = (req.headers.get("x-fxmd-signature") ?? "").replace(/^v1=/, "");
const secret = Deno.env.get("FXMD_WEBHOOK_SECRET")!;
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const expected = await hmacHex(secret, `${ts}.${eventId}.${body}`);
return safeEqual(expected, supplied);
}
The Edge Function receiver should store the event ID before doing any heavier work. That keeps webhook retries from creating duplicate alerts or repeated dashboard refreshes.
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
Deno.serve(async (req) => {
const body = await req.text();
if (!(await verifyFxmd(req, body))) return new Response("invalid", { status: 401 });
const payload = JSON.parse(body);
const event = payload.data ?? {};
const db = createClient(Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!);
const { error } = await db.from("fxmd_macro_events").upsert({
event_id: event.event_id,
source: "webhook",
currency: event.currency,
indicator: event.indicator,
announcement_at: event.latest_announcement?.announcement_datetime,
payload
}, { onConflict: "event_id", ignoreDuplicates: true });
return new Response(error ? "store failed" : null, { status: error ? 500 : 204 });
});
Full webhook setup, retry behavior, delivery logs, and signature headers are covered in the FXMacroData webhooks documentation and the webhooks announcement article.
6. Poll Recent Changes from Edge Functions
If inbound webhooks are not possible, use the changes endpoint instead of polling every currency and indicator. The endpoint returns recent release events from the same live fanout used by SSE. Store next_cursor and pass it back as since on the next poll.
curl "https://api.fxmacrodata.com/v1/announcements/changes?currencies=g10&payload=full&limit=100&api_key=YOUR_API_KEY"
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
Deno.serve(async () => {
const db = createClient(Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!);
const apiKey = Deno.env.get("FXMD_API_KEY")!;
const { data: state } = await db.from("fxmd_integration_state")
.select("cursor").eq("name", "g10_changes").maybeSingle();
const url = new URL("https://api.fxmacrodata.com/v1/announcements/changes");
for (const [key, value] of [["currencies", "g10"], ["payload", "full"],
["limit", "100"], ["api_key", apiKey]]) url.searchParams.set(key, value);
if (state?.cursor) url.searchParams.set("since", state.cursor);
const json = await (await fetch(url)).json();
for (const event of json.data ?? []) {
await db.from("fxmd_macro_events").upsert({
event_id: event.event_id, source: "changes",
currency: event.currency, indicator: event.indicator, payload: event
}, { onConflict: "event_id", ignoreDuplicates: true });
}
await db.from("fxmd_integration_state")
.upsert({ name: "g10_changes", cursor: json.next_cursor });
return Response.json({ count: json.count, has_more: json.has_more });
});
Schedule the poller with Supabase scheduled functions. During quiet hours, the response will normally be empty but cheap to process. During release windows, one request can capture multiple currencies and indicators without scanning every slug.
select cron.schedule(
'fxmd-g10-changes',
'*/5 * * * *',
$$
select net.http_post(
url := 'https://PROJECT_REF.functions.supabase.co/fxmd-changes-poller',
headers := '{"Authorization":"Bearer YOUR_SUPABASE_FUNCTION_SECRET"}'::jsonb
);
$$
);
If the cursor is older than the endpoint retention window, run the bootstrap function again and continue with the newest next_cursor. That is simpler and safer than trying to recover missed releases by expanding per-indicator polling.
7. Use SSE from an Always-On Worker
FXMacroData SSE streams are useful when a process can keep a connection open and reconnect with Last-Event-ID. Supabase Edge Functions are request handlers, and Supabase documents separate limits for duration, idle timeout, memory, and CPU. For that reason, do not build the SSE client inside an Edge Function.
Run the stream listener in an always-on process instead:
curl -N "https://api.fxmacrodata.com/v1/stream/events?currencies=g10&payload=full&api_key=YOUR_API_KEY"
The worker can be small: connect to https://api.fxmacrodata.com/v1/stream/events, parse announcement events, write each event_id into fxmd_macro_events, and reconnect using the last received event ID. Use SSE stream documentation when you need this path.
8. Production Checklist
Only Edge Functions or always-on workers should call FXMacroData with your API key.
Use event_id as the primary key so webhooks, retries, SSE reconnects, and polling overlap safely.
Use webhooks or changes polling instead of calling /calendar/ and /announcements/ for every slug every minute.
Let your app read display-ready rows from Supabase rather than calling FXMacroData from every page view.
Common Questions
Should I use webhooks or the changes endpoint with Supabase?
Use webhooks when your Supabase Edge Function can receive public HTTPS callbacks. Use /v1/announcements/changes when you need a scheduled serverless poller instead.
Can I run the FXMacroData SSE stream inside a Supabase Edge Function?
Use an always-on worker for SSE. Edge Functions are better suited to webhook receivers, REST proxies, and scheduled jobs that finish quickly.
Do I need to load historical data every night?
Usually no. Bootstrap current state with /latest, store release events as they arrive, and fetch historical series only for the indicators your product actually displays or analyzes.
Which FXMacroData endpoint should a Supabase cron job poll?
Prefer https://api.fxmacrodata.com/v1/announcements/changes with a stored cursor. It is designed for serverless clients that cannot keep an SSE connection open.
Get Started
Start with the webhook path if your Supabase project can expose a public Edge Function. Add the changes poller as a fallback for missed callbacks or environments that cannot receive inbound webhooks. Use REST /latest for bootstrap and recovery, and keep SSE in a small always-on worker when you need a continuous stream.
Reference pages: FXMacroData webhooks, FXMacroData SSE streams, announcement endpoints, and Supabase Edge Functions.