usePaginatedQuery shows them in
React as they arrive.
Xquik is the world’s fastest and cheapest X Twitter scraper service with the
most complete X data. It bills only for delivered, unique results that match
your filters. Reads cost from $0.00015 / tweet.
convex-xquik is a
Convex component. Its source is public on
GitHub. Xquik does not require an
X Developer app.
Typed actions
Each of the 190 public operations is an action with validated arguments.
Signed events
The component’s receiver verifies each event and stores it once.
Durable jobs
xquik.schedule runs any operation once or on a repeat.Live queries
usePaginatedQuery shows new events as they arrive.Key in the component
The component reads
XQUIK_API_KEY only inside its own functions.Tests
convex-xquik/test adds the component to convex-test.Install the component
Create an Xquik API key in the dashboard. Then install the package in your Convex app.convex/convex.config.ts.
convex/convex.config.ts
httpPrefix mounts
its webhook receiver under /xquik/ on your deployment’s .convex.site URL.
Call the Twitter API from a Convex action
Each public operation is an action incomponents.xquik.operations. Its name
is the operation ID from the API reference. Call it with
ctx.runAction from your own action.
convex/tweets.ts
- Arguments are the operation’s path, query, header, and JSON body fields side by side.
- Convex validates the arguments before the call.
- Binary exports return an
ArrayBuffer. streamEventsreads the event stream for up to 10 seconds. Then it returns the events.
ConvexError. Its data has these fields.
Error Handling explains each error code.
Receive signed Twitter webhook events
Xquik is the component’s client class. Create it once from
components.xquik.
Create a monitor for an account, then call xquik.createWebhook from an action.
It creates an Xquik webhook that points at the component’s receiver. A monitor
bills while it is active, as Billing explains.
convex/x.ts
createWebhook returns { endpointId, url, webhookId }. The signing secret
stays in the component’s own table. It never reaches your code.
onEvent is optional. The component runs it once for each streamEventId an
endpoint receives. If 2 endpoints receive the same event, each endpoint’s
handler runs once.
Run the testWebhook operation with the returned webhookId as id. The
receiver answers the webhook.test alert with 200 and records testedAt on
the endpoint. Read it with components.xquik.webhooks.list. That query returns
testedAt as null until the first test. For a webhook from
xquik.addWebhook, the query returns webhookId as null.
components.xquik.webhooks.remove deletes the Xquik webhook and the endpoint.
Register a webhook made elsewhere
xquik.addWebhook registers a webhook you made somewhere else. Use it for a
webhook from the dashboard or an Apify Monitor run. Call it from a mutation
with the webhook’s secret. It returns the URL to paste there. Add it to
convex/x.ts.
convex/x.ts
How the receiver handles each delivery
The receiver answersPOST https://YOUR_DEPLOYMENT.convex.site/xquik/webhooks/TOKEN.
Signature check
It checks the
X-Xquik-Signature header. That HMAC-SHA256 covers the
millisecond timestamp, the nonce, and the raw body.5-minute window
It rejects a timestamp more than 5 minutes off.
Nonce once
It rejects any nonce it has seen. A cron deletes expired nonces every 10
minutes.
Stored once
It stores each
streamEventId once, so retries never add a second event.Schedule durable Twitter API jobs
xquik.schedule starts a durable job from a mutation. Pass the operation ID,
its arguments, and the timing. Add it to convex/x.ts.
convex/x.ts
keyallows 1 scheduled or running job per key. Another call with that key returns that job’s ID. The job keeps its first arguments and timing.- If a keyed job’s run crashed, the next call with that key replaces it.
everyMsrepeats the job. It takes 60000 or more.delayMsdelays the first run.- The job stores each run’s result. Read it with
components.xquik.jobs.getorcomponents.xquik.jobs.list. components.xquik.jobs.cancelstops a job.- A read that gets
429or5xxretries up to 5 times. Each retry waits longer. - A write that takes an
Idempotency-Keyretries the same way. The component fills the key for you.
listEvents jobs store their events with the webhook events. Local
development sees events without a public URL. The component runs no onEvent
handler for an event from a listEvents job. If a webhook later delivers that
event, the endpoint’s handler still runs once.
Show Twitter events live in React
components.xquik.events.list returns stored events, newest first. Wrap it in
your own query, with your own auth check.
convex/events.ts
usePaginatedQuery. The list updates as events arrive.
- Pass
filterwith{ eventType }or{ monitorId }to narrowevents.list. components.xquik.events.gettakes{ streamEventId }and returns that event.
Protect the functions you expose
Only your backend can call component functions. Clients call only the functions you export. Add your own auth check to each one, as thelist query
above does. ctx.auth.getUserIdentity() returns the signed-in user.
Test your functions with convex-test
xquikTest.register(t) adds the component to a convexTest instance.
convex/x.test.ts
Common Convex Twitter API questions
How do I call the Twitter API from Convex?
Installconvex-xquik and set XQUIK_API_KEY. Then call an operation such as
components.xquik.operations.getUser with ctx.runAction from an action.
Can I call the Twitter API from a query or mutation?
No. Convex runs network calls only in actions. From a mutation, start a job withxquik.schedule.
How do I receive Twitter webhooks in Convex?
Callxquik.createWebhook from an action. The component’s receiver verifies
and stores each event.
Does a retried delivery create a duplicate event?
No. The receiver stores eachstreamEventId once.
How do I see events in local development?
Schedule alistEvents job. Its events land with the webhook events, so you
need no public URL.
What happens when Xquik answers 429?
The call throws aConvexError with retryAfter in seconds. Scheduled reads
retry a 429 up to 5 times. So do writes that take an Idempotency-Key.