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:
- Log in to the Quasar Dashboard .
- Select your target Organization and App.
- Navigate to Webhooks and click Add Endpoint.
- Enter your destination URL (e.g.
https://api.myapp.com/webhooks/quasar). - Select the Event Triggers you want to subscribe to (
transaction:success,transaction:failed,transaction:replaced, or*for all events). - 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 Identifier | Description |
|---|---|
* | All Events — Subscribes to all transaction lifecycle events. |
transaction:success | Triggered when a tracked transaction mines successfully and is confirmed on-chain. |
transaction:failed | Triggered when a transaction execution fails or reverts. |
transaction:replaced | Triggered when a pending transaction is replaced/cancelled in the wallet (EVM speedup). |
📨 Delivery Headers
Every HTTP POST dispatch includes standard HTTP headers:
| Header Name | Description |
|---|---|
Content-Type | application/json |
User-Agent | Quasar-Webhook-Worker/1.0 (TuwaIO) |
x-quasar-signature | The HMAC SHA-256 signature of the raw JSON body calculated using your Signing Secret. |
x-quasar-event | The 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 assecret,token,password,key,privatekey,secretkey,apikey,signingsecret,credential) are automatically scrubbed and replaced with"***MASKED***"before database persistence.