Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.xquik.com/llms.txt

Use this file to discover all available pages before exploring further.

Test your webhook integration before deploying to production. Use Xquik’s signed test delivery, local HTTPS tunnels, payload inspectors, and delivery logs to verify handlers before real monitor events arrive.

Test with Xquik first

Use the Test Webhook endpoint after you create a webhook. Xquik sends a real webhook.test delivery to the configured URL with the same X-Xquik-Signature, X-Xquik-Timestamp, and X-Xquik-Nonce headers used for production monitor events. webhook.test payloads contain eventType, data.message, and timestamp; they omit deliveryId and streamEventId, so use them for reachability and signature checks, not production de-dupe.
curl -X POST https://xquik.com/api/v1/webhooks/15/test \
  -H "x-api-key: xq_YOUR_KEY_HERE" | jq
Response when your endpoint accepts the delivery:
{
  "success": true,
  "statusCode": 200
}
Response when your endpoint rejects the delivery:
{
  "success": false,
  "statusCode": 500,
  "error": "HTTP 500"
}
Check your server logs for the webhook.test payload, verify the HMAC before processing it, and return a 2xx response only after your handler accepts the event.

Local testing with ngrok

ngrok creates a public HTTPS tunnel to your local server, letting Xquik deliver webhooks to your development machine.
1

Install ngrok

# macOS
brew install ngrok

# Linux
curl -sSL https://ngrok-agent.s3.amazonaws.com/ngrok-v3-stable-linux-amd64.tgz \
  | tar -xz -C /usr/local/bin

# Authenticate (free account required)
ngrok config add-authtoken YOUR_NGROK_TOKEN
2

Start your local server

Run your webhook handler on a local port (e.g. :3000):
node server.js
# or: python app.py
# or: go run main.go
3

Start the ngrok tunnel

ngrok http 3000
ngrok outputs a public HTTPS URL:
Forwarding  https://a1b2c3d4.ngrok-free.app → http://localhost:3000
Copy the https:// URL.
4

Create a webhook with the ngrok URL

curl -X POST https://xquik.com/api/v1/webhooks \
  -H "x-api-key: xq_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://a1b2c3d4.ngrok-free.app/webhook",
    "eventTypes": ["tweet.new", "tweet.reply"]
  }' | jq
Save the secret from the response for signature verification.
5

Send a signed test delivery

Trigger a signed webhook.test request through the tunnel before waiting for real tweet events:
curl -X POST https://xquik.com/api/v1/webhooks/15/test \
  -H "x-api-key: xq_YOUR_KEY_HERE" | jq
Inspect your app logs and the ngrok web inspector at http://localhost:4040.
6

Trigger events

When a monitored account posts a tweet, Xquik delivers the event through ngrok to your local server. Check your server logs and the ngrok web inspector at http://localhost:4040.
ngrok URLs change every time you restart the tunnel (free plan). Update your webhook URL after each restart, or use a paid ngrok plan for stable subdomains.

Testing with webhook.site

Use webhook.site to inspect webhook payloads without running a local server.
1

Get a webhook.site URL

Visit webhook.site. A unique HTTPS URL is generated automatically:
https://webhook.site/a1b2c3d4-e5f6-7890-abcd-ef1234567890
2

Create a webhook with the webhook.site URL

curl -X POST https://xquik.com/api/v1/webhooks \
  -H "x-api-key: xq_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://webhook.site/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "eventTypes": ["tweet.new"]
  }' | jq
3

View incoming payloads

When events arrive, they appear in the webhook.site dashboard in real time. Inspect headers (X-Xquik-Signature, Content-Type) and the JSON body to verify the payload format matches your expectations.
webhook.site is useful for inspecting payload structure. For testing signature verification and handler logic, use ngrok with a local server instead.

Sending test payloads

Simulate a webhook delivery to your local handler without calling Xquik. This is useful for unit tests or offline debugging. For end-to-end verification of the configured webhook URL, prefer POST /webhooks/{id}/test.
# Generate a test signature
SECRET="your_webhook_secret_here"
PAYLOAD=$(cat <<'JSON'
{"eventType":"tweet.new","schemaVersion":1,"deliveryId":"502","streamEventId":"9002","occurredAt":"2026-02-24T14:22:00.000Z","username":"elonmusk","data":{"id":"1893456789012345678","text":"The future is now.","author":{"id":"44196397","userName":"elonmusk","name":"Elon Musk"},"isRetweet":false,"isReply":false,"isQuote":false,"createdAt":"2026-02-24T14:22:00.000Z"}}
JSON
)
TIMESTAMP=$(date +%s)000
NONCE=$(openssl rand -hex 16)

SIGNATURE="sha256=$(printf '%s.%s.%s' "$TIMESTAMP" "$NONCE" "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET" | cut -d' ' -f2)"

# Send to your local server
curl -X POST http://localhost:3000/webhook \
  -H "Content-Type: application/json" \
  -H "X-Xquik-Timestamp: $TIMESTAMP" \
  -H "X-Xquik-Nonce: $NONCE" \
  -H "X-Xquik-Signature: $SIGNATURE" \
  -d "$PAYLOAD"
Test payload structure:
{
  "eventType": "tweet.new",
  "schemaVersion": 1,
  "deliveryId": "502",
  "streamEventId": "9002",
  "occurredAt": "2026-02-24T14:22:00.000Z",
  "username": "elonmusk",
  "data": {
    "id": "1893456789012345678",
    "text": "The future is now.",
    "author": {
      "id": "44196397",
      "userName": "elonmusk",
      "name": "Elon Musk"
    },
    "isRetweet": false,
    "isReply": false,
    "isQuote": false,
    "createdAt": "2026-02-24T14:22:00.000Z"
  }
}
Include deliveryId and streamEventId in offline fixtures so receiver idempotency tests match production deliveries. Test all event types by changing the eventType field: tweet.new, tweet.reply, tweet.quote, tweet.retweet.

Debugging delivery failures

Check delivery status

Query the deliveries endpoint to see delivery attempts and error details:
curl https://xquik.com/api/v1/webhooks/15/deliveries \
  -H "x-api-key: xq_YOUR_KEY_HERE" | jq
Response:
{
  "deliveries": [
    {
      "id": "502",
      "streamEventId": "9002",
      "status": "failed",
      "attempts": 3,
      "lastStatusCode": 500,
      "lastError": "Internal Server Error",
      "createdAt": "2026-02-24T14:25:00.000Z"
    }
  ]
}

Delivery statuses

pending

Queued for the next delivery attempt. Check recent deploys, tunnel uptime, and receiver availability before forcing a new test.

delivered

Your endpoint returned 2xx. Confirm your handler verified the signature and stored the event before it returned success.

failed

The latest attempt failed and is retrying with backoff. Inspect lastStatusCode, lastError, and your receiver logs.

exhausted

All retry attempts are used, or the receiver returned 410 Gone. Fix the endpoint, then send a new signed test delivery.

Common failure reasons

We recommend responding within 10 seconds. If your handler is slow, return 200 immediately and process the event asynchronously using a background job queue.
Any response outside the 200-299 range counts as a failure. Check your server logs for unhandled exceptions or validation errors in your handler. Common culprits: missing middleware (e.g. express.raw()), JSON parse errors, or database connection failures.
The webhook URL hostname could not be resolved. Verify your domain DNS records are correct. If using ngrok, confirm the tunnel is still running.
Webhook URLs must use HTTPS with a valid certificate. Self-signed certificates are rejected. Use a trusted CA (Let’s Encrypt, Cloudflare) or ngrok for local development.
The target server is not accepting connections. Verify your server is running and listening on the correct port. Check firewall rules if running on a cloud provider.
Compute the HMAC over the raw request body bytes, not a re-serialized JSON object. Re-serialization can alter whitespace or key ordering. Use express.raw() in Node.js, request.get_data() in Flask, or io.ReadAll(r.Body) in Go.

Webhooks Overview

How webhooks work, delivery format, and retry policy.

Signature Verification

HMAC-SHA256 verification in Node.js, Python, and Go.

Deliveries API

Query delivery attempts and statuses.
Last modified on May 19, 2026