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.
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.
{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.
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.
public.xquik_actions. The xquik-collect job copies
each finished response into that row every 10 seconds.
cron.unschedule.
Run actions from row inserts
Insert a row intopublic.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.
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
Thexquik-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.
secret only once. Store it as an Edge Function
secret, then send a test delivery.
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.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.
Common Supabase Twitter API questions
How do I call the Twitter API from an Edge Function?
CallcreateXquik() 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. Runxquik.call for 1 JSON response or xquik.rows for 1 row per item.
How do I schedule Twitter API reads in Supabase?
Runxquik.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. Deployxquik-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 withsupabase secrets set XQUIK_WEBHOOK_SECRET.
Does a retried delivery create a duplicate row?
No. The function stores eachstreamEventId once and answers 200 for
repeats.