Skip to main content
Supabase is a Postgres platform with Edge Functions, Vault, and pg_cron. This guide brings the Twitter API into a Supabase project through Xquik. You read tweets and profiles from SQL, schedule reads with pg_cron, and run actions from row inserts. Signed X webhook events land in a Postgres table, each one stored once. supabase-xquik is a ready Supabase project. Clone it and start it with 1 command. Xquik does not require an X Developer app.

Typed calls in Edge Functions

The x-twitter-scraper SDK gives typed calls to every public operation.

SQL calls

xquik.call and xquik.rows reach every public operation from SQL.

Schedules

xquik.schedule runs any operation on a pg_cron schedule.

Actions from rows

Insert a row into xquik_actions and the action runs.

Signed events

The xquik-webhook Edge Function verifies each event and stores it once.

Key in Vault

SQL reads the API key from Vault. It never sits in a table.

Start the project locally

Create an Xquik API key. Install Docker, then clone the project.
supabase start applies the migrations and serves the Edge Functions. The key goes to Vault for SQL and to the Edge Function secrets. The webhook secret goes to the receiver. The CLI also reads both values from a .env file in the project root.

Call the Twitter API with the TypeScript SDK

createXquik in _shared/xquik.ts creates the typed SDK client from XQUIK_API_KEY. Import it from any function in supabase/functions.
Each SDK method maps to 1 API reference operation. Your editor completes parameters and response fields.

Read Twitter data with SQL

xquik.call sends 1 operation and returns its JSON response. Pass the operation ID from the API reference and a JSON object of parameters.
xquik.rows returns the response list as 1 row per item. Use it for search, timelines, and other list operations.
Path parameters such as {id} fill from the JSON object. The rest go in the query string for GET and DELETE, or in the JSON body otherwise. A failed call raises an error with Xquik’s error and message fields. The xquik.operations table lists every operation these functions accept.
Only the service_role and the database owner can run xquik functions. Browser clients with the anon key cannot reach them.

Schedule Twitter API reads with pg_cron

xquik.schedule creates a pg_cron job that sends 1 operation on a cron schedule. Run it again with the same job name to change the job.
Each run adds a row to public.xquik_actions. The xquik-collect job copies each finished response into that row every 10 seconds.
Remove a schedule with cron.unschedule.

Run actions from row inserts

Insert a row into public.xquik_actions to run an operation. A trigger sends it as the row goes in, so the row lands as sent. Then xquik-collect marks it succeeded or failed.
xquik.enqueue does the same insert and returns the action ID.
Actions and schedules run GET, POST, and DELETE operations. Run PUT and PATCH operations with xquik.call. An app, a trigger, or a Supabase Database Webhook can add these rows. The table has row level security with no policies. Insert from your server with the service role key.

Receive signed Twitter webhook events

The xquik-webhook Edge Function receives Xquik monitor events. It verifies each signature and stores each event once in public.xquik_events. Create a monitor for an account. Then point a webhook at the function URL. A monitor bills while it is active, as Billing explains.
The webhook response returns secret only once. Store it as an Edge Function secret, then send a test delivery.
Read stored events in the order they happened.
The function answers each delivery with 1 status. A 503 makes Xquik retry the delivery later.

Handle webhook edge cases

The function handles each case below. Keep the behavior when you edit it.

No Supabase JWT check

Xquik sends no Supabase JWT, so the default check answers 401. The project sets verify_jwt = false for xquik-webhook. The HMAC signature authenticates each delivery instead.

Raw body

The function verifies the raw request bytes before it parses JSON. Re-serialized JSON can change bytes and fail the check.

Milliseconds

X-Xquik-Timestamp is Unix time in milliseconds. A value in seconds looks decades old and fails the 5-minute window.

Fast 2xx

Xquik waits 10 seconds for a 2xx. The function stores the event and answers. Put slow work in onEvent, which runs after the response.

Signed retries

A retry keeps the body, deliveryId, and streamEventId. It gets a new timestamp, nonce, and signature, so it passes the nonce check.

Dedupe on streamEventId

stream_event_id is the primary key of xquik_events. A retry or a second webhook adds no second row.

Order by occurredAt

Deliveries can arrive in any order. Sort by occurred_at, then stream_event_id. An index covers that order.

Test alert

webhook.test has no deliveryId or streamEventId. The function answers 200 and stores nothing.

Several URLs

Each webhook has its own secret. Put every secret in XQUIK_WEBHOOK_SECRET, separated by commas.

Secret rotation

Create a new webhook for the same URL and add its secret. Delete the old webhook, then remove its secret.

No secrets in logs

The function never logs the secret, the signature, or the headers. Keep them out of any logging you add.

Nonce cleanup

The xquik-nonce-cleanup job deletes nonces older than 10 minutes. It runs every 5 minutes.
Signature verification explains the signing string and the replay window.

Deploy to a hosted Supabase project

Link the clone to your project and push the migrations.
functions deploy reads verify_jwt = false for xquik-webhook from supabase/config.toml. Then store the API key in Vault from the SQL editor.
The SQL functions read the key from Vault. The Edge Functions read it from their secrets.

Common Supabase Twitter API questions

How do I call the Twitter API from an Edge Function?

Call createXquik() from _shared/xquik.ts. Then call any SDK method, such as xquik.x.users.retrieve("NASA").

Can I call the Twitter API from Postgres?

Yes. Run xquik.call for 1 JSON response or xquik.rows for 1 row per item.

How do I schedule Twitter API reads in Supabase?

Run xquik.schedule with a job name, a cron schedule, and an operation ID. Results land in public.xquik_actions.

Why does my Supabase webhook function return 401?

The Supabase JWT check rejects the delivery before your code runs. Deploy xquik-webhook with verify_jwt = false. A wrong secret also returns 401.

Where do I find the webhook secret?

The Create Webhook response returns it once. Store it with supabase secrets set XQUIK_WEBHOOK_SECRET.

Does a retried delivery create a duplicate row?

No. The function stores each streamEventId once and answers 200 for repeats.

Is this Supabase Twitter login?

No. This guide reads X data and receives X events. Use Supabase Auth for sign-in with X.

Source and next steps