Skip to Content
Webhooks & OutboxWebhooks & Triggers

Webhooks Overview

Quasar operates as a high-performance, headless transaction tracking engine. To inform your backend when asynchronous on-chain transaction lifecycles complete, Quasar uses Webhooks to push real-time event notifications directly to your application endpoints.


⚡ How Webhooks Work

Instead of polling the Quasar API to track transaction progress, you register an HTTP endpoint (e.g. https://api.myapp.com/webhooks/quasar) in the Quasar Dashboard.

When a subscribed event occurs (such as a transaction confirming or failing), Quasar enqueues a delivery job in its background worker queue (BullMQ + Redis), signs the payload using an HMAC SHA-256 secret, and dispatches an HTTP POST request to your endpoint.


🛠️ Registering a Webhook Endpoint

To register a webhook endpoint in the Quasar Dashboard:

  1. Log in to the Quasar Dashboard .
  2. Select your target Organization and App.
  3. Navigate to Webhooks and click Add Endpoint.
  4. Enter your destination URL (e.g. https://api.myapp.com/webhooks/quasar).
  5. Select the Event Triggers you want to subscribe to (transaction:success, transaction:failed, transaction:replaced, or * for all events).
  6. Save the endpoint and copy the auto-generated Signing Secret (whsec_...).

Endpoint Limit: Each application container can manage up to 500 active webhook endpoints. Production endpoints must use https:// URLs (http:// is allowed for local development only).


📡 Event Triggers

Webhooks in Quasar use standard event identifier strings:

Event IdentifierDescription
*All Events — Subscribes to all transaction lifecycle events.
transaction:successTriggered when a tracked transaction mines successfully and is confirmed on-chain.
transaction:failedTriggered when a transaction execution fails or reverts.
transaction:replacedTriggered when a pending transaction is replaced/cancelled in the wallet (EVM speedup).

📨 Delivery Headers

Every HTTP POST dispatch includes standard HTTP headers:

Header NameDescription
Content-Typeapplication/json
User-AgentQuasar-Webhook-Worker/1.0 (TuwaIO)
x-quasar-signatureThe HMAC SHA-256 signature of the raw JSON body calculated using your Signing Secret.
x-quasar-eventThe event type identifier string (e.g. transaction:success).

📦 Webhook Payload Structure

Dispatched webhook payloads contain transaction metadata alongside custom app payload fields:

{ "txKey": "tx_local_18f29ab4c7...", "hash": "0xabc1234567890def...", "status": "Success", "action": "transaction:success", "txType": "SWAP", "chainId": "1", "timestamp": 1779374022, "payload": { "tokenIn": "USDC", "tokenOut": "ETH", "amount": 100 } }

🔐 Webhook Signing Secret (whsec_...)

When a webhook endpoint is created, Quasar generates a cryptographically secure 256-bit signing secret prefixed with whsec_:

whsec_a1b2c3d4e5f678901234567890abcdef...

Security at Rest: Signing secrets are encrypted before storage in PostgreSQL using symmetric AES-256 (qenc: format).

Your backend must verify the x-quasar-signature header against your stored signing secret to ensure incoming HTTP requests originate from Quasar and have not been tampered with.


💻 Webhook Receiver Verification Example

Below is a complete, production-grade Next.js App Router API Route Handler (src/app/api/webhooks/quasar/route.ts) demonstrating timing-safe signature verification:

// src/app/api/webhooks/quasar/route.ts import { NextRequest, NextResponse } from 'next/server'; import crypto from 'crypto'; export async function POST(req: NextRequest) { const signatureHeader = req.headers.get('x-quasar-signature'); const eventHeader = req.headers.get('x-quasar-event'); const signingSecret = process.env.QUASAR_WEBHOOK_SECRET; if (!signatureHeader || !signingSecret) { return NextResponse.json({ error: 'Unauthorized: Missing signature or secret' }, { status: 401 }); } // 1. Extract raw body text to preserve exact character spacing for HMAC calculation const rawBody = await req.text(); // 2. Compute expected HMAC SHA-256 hex digest const expectedSignature = crypto.createHmac('sha256', signingSecret).update(rawBody).digest('hex'); // 3. Perform timing-safe comparison to prevent side-channel timing attacks const signatureBuffer = Buffer.from(signatureHeader, 'hex'); const expectedBuffer = Buffer.from(expectedSignature, 'hex'); if (signatureBuffer.length !== expectedBuffer.length || !crypto.timingSafeEqual(signatureBuffer, expectedBuffer)) { return NextResponse.json({ error: 'Invalid webhook signature' }, { status: 401 }); } // 4. Parse verified payload const payload = JSON.parse(rawBody); console.log(`[Webhook] Received verified event "${eventHeader}" for txKey: ${payload.txKey}`); // Process event in your business logic... if (eventHeader === 'transaction:success') { // Handle success... } // Always return HTTP 200 OK promptly return NextResponse.json({ success: true }, { status: 200 }); }

🚀 Worker Performance, Retries & Data Masking

Quasar dispatches webhooks via an isolated NestJS worker tier using BullMQ and Redis:

  • Concurrency & Rate Limiting: Workers execute at up to 15 concurrent jobs per node with a global rate limit of 200 dispatches per second (WEBHOOK_LIMITER_MAX).
  • Retries & Backoff: Failed deliveries (network errors or non-2xx status codes) are retried automatically up to 5 times using exponential backoff.
  • Manual Resend: In addition to automated retries, failed webhooks can be manually restarted directly from the Quasar Dashboard interface via the Webhook Delivery Logs.
  • Quota Billing Weight: Webhook dispatches carry a quota weight of 1.0 unit per event. Quota is charged only on the initial delivery attempt; retries do not consume additional quota units.
  • Automated Data Masking: Webhook delivery logs (webhook_deliveries) store request and response details for auditability. Sensitive keys inside request or response bodies (such as secret, token, password, key, privatekey, secretkey, apikey, signingsecret, credential) are automatically scrubbed and replaced with "***MASKED***" before database persistence.
Last updated on