Skip to main content
With webhook mode on, an x_list Connector Instance receives the posts of its list’s members within seconds, through an X Filtered Stream linked to a webhook. Polling stays on as the fallback. Read the X list guide first: everything there still applies.

What you bring

  • A public URL for the API. Set public_url in the QUIVR_CONFIG of every api and worker process to the address where X reaches your API, for example https://quivr.example.com. Each instance’s webhook address is <public_url>/v0/connector-webhooks/<connector_id>; the instance read shows it as webhook_url. The route needs no API key, because X signs every request. Put it behind your usual rate limiting.
  • The x-list plugin reachable from the API. The API relays each delivery to the plugin, so pin it on the api processes too (the Railway image runs it beside the API).
  • An app with Filtered Stream and webhooks. Your X plan must include the Filtered Stream and webhook endpoints. The bearer token must manage the app’s stream rules and webhooks.
  • The consumer secret, deposited with the bearer token (consumer_secret). The plugin answers X’s CRC check and verifies every delivery’s x-twitter-webhooks-signature with it.

Turn it on

Add a webhook block to the instance config: Set max_rules and max_rule_length to your plan’s limits. Rules are shared by every instance of the same X app, so leave room for the others. Deletion rechecks and resyncs run during polls, so while webhooks work they also wait up to poll_interval_seconds: keep it within your recheck needs.

What the plugin sets up

During pull runs, the plugin reads the list members and turns them into rules from:<user id> OR …, each within max_rule_length, tagged quivr:<connector_id>. It adds and deletes only what changed, so a resync with the same members writes nothing. It registers the webhook address (X checks it with a CRC request) and links it to the stream. A list that needs more than max_rules rules stays on polling (rule_limit_exceeded). Deliveries go through the same mapping as polling. A post seen both ways is one Record with one Version.

Health and fallback

health.push shows the webhook side: An access error shows as the access_error Connector Health state while polling keeps collecting. To avoid false alarms, polling holds back posts younger than one minute while webhooks work. A member added to the list shows up at the next resync.

Check it with a real account

CI tests webhook mode against a local fake X only. To check a real account: turn it on for a test list, wait for health.push.state active, post from a member, and watch the Record arrive within seconds. Then compare health.usage with the X console.

Turning it off

Set enabled to false, or disable the instance. The rules tagged quivr:<connector_id> and the webhook stay at X until you delete them in the developer console or through the X API.