Skip to main content
Convex is a TypeScript backend with a reactive database, actions, and schedules. This guide brings the Twitter API into a Convex app through Xquik. You call every public operation as a typed action. Signed X webhook events land in Convex tables, each one stored once. 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.
Add the component in convex/convex.config.ts.
convex/convex.config.ts
Store the key as a deployment environment variable.
The component reads the key only inside its own functions. 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 in components.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.
  • streamEvents reads the event stream for up to 10 seconds. Then it returns the events.
A failed call throws a 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 answers POST 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.
Signature verification explains the signing string and the replay window.

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
  • key allows 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.
  • everyMs repeats the job. It takes 60000 or more.
  • delayMs delays the first run.
  • The job stores each run’s result. Read it with components.xquik.jobs.get or components.xquik.jobs.list.
  • components.xquik.jobs.cancel stops a job.
  • A read that gets 429 or 5xx retries up to 5 times. Each retry waits longer.
  • A write that takes an Idempotency-Key retries the same way. The component fills the key for you.
Scheduled 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
Read it with usePaginatedQuery. The list updates as events arrive.
  • Pass filter with { eventType } or { monitorId } to narrow events.list.
  • components.xquik.events.get takes { 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 the list 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?

Install convex-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 with xquik.schedule.

How do I receive Twitter webhooks in Convex?

Call xquik.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 each streamEventId once.

How do I see events in local development?

Schedule a listEvents job. Its events land with the webhook events, so you need no public URL.

What happens when Xquik answers 429?

The call throws a ConvexError with retryAfter in seconds. Scheduled reads retry a 429 up to 5 times. So do writes that take an Idempotency-Key.

Is this Convex sign-in with X?

No. This guide reads X data and receives X events. Use your auth provider for sign-in with X.

Source and next steps