Webhook Verification
Always verify webhook signatures to ensure requests are genuinely from Tanqory.
verifyWebhookSignature()
import { verifyWebhookSignature } from '@tanqory/sdk'
const isValid = verifyWebhookSignature(rawBody, signature, secret)
| Parameter | Type | Description |
|---|---|---|
rawBody | string or Buffer | The raw request body (not parsed JSON) |
signature | string | Value of the x-tanqory-signature header |
secret | string | Webhook secret from the dashboard |
Returns true if the signature is valid, false otherwise.
Express.js Example
import express from 'express'
import { verifyWebhookSignature } from '@tanqory/sdk'
const app = express()
// Use express.raw() to get the raw body — NOT express.json()
app.post('/webhooks/tanqory', express.raw({ type: 'application/json' }), (req, res) => {
const isValid = verifyWebhookSignature(
req.body,
req.headers['x-tanqory-signature'] as string,
process.env.TANQORY_WEBHOOK_SECRET!,
)
if (!isValid) {
return res.status(401).send('Invalid signature')
}
const event = JSON.parse(req.body.toString())
switch (event.event) {
case 'order.created':
handleNewOrder(event.data)
break
case 'product.updated':
handleProductUpdate(event.data)
break
case 'inventory.low_stock':
handleLowStock(event.data)
break
}
res.status(200).send('OK')
})
Next.js API Route Example
// app/api/webhooks/tanqory/route.ts
import { verifyWebhookSignature } from '@tanqory/sdk'
import { NextRequest, NextResponse } from 'next/server'
export async function POST(req: NextRequest) {
const rawBody = await req.text()
const signature = req.headers.get('x-tanqory-signature')
const isValid = verifyWebhookSignature(
rawBody,
signature!,
process.env.TANQORY_WEBHOOK_SECRET!,
)
if (!isValid) {
return NextResponse.json({ error: 'Invalid signature' }, { status: 401 })
}
const event = JSON.parse(rawBody)
// Process event...
return NextResponse.json({ received: true })
}
NestJS Example
import { Controller, Post, Req, Res, HttpStatus } from '@nestjs/common'
import { Request, Response } from 'express'
import { verifyWebhookSignature } from '@tanqory/sdk'
@Controller('webhooks')
export class WebhookController {
@Post('tanqory')
handleWebhook(@Req() req: Request, @Res() res: Response) {
const isValid = verifyWebhookSignature(
req.body,
req.headers['x-tanqory-signature'] as string,
process.env.TANQORY_WEBHOOK_SECRET!,
)
if (!isValid) {
return res.status(HttpStatus.UNAUTHORIZED).send('Invalid signature')
}
const event = JSON.parse(req.body.toString())
// Process event...
return res.status(HttpStatus.OK).send('OK')
}
}
How It Works
The signature is an HMAC-SHA256 hex digest:
HMAC-SHA256(webhook_secret, raw_request_body) → hex string
The SDK compares this against the x-tanqory-signature header using a timing-safe comparison to prevent timing attacks.
Common Mistakes
| Mistake | Fix |
|---|---|
| Parsing body before verification | Use express.raw() or req.text() to get the raw body |
Using express.json() middleware globally | Exclude the webhook route from JSON parsing |
| Wrong secret | Use the secret from the webhook creation response, not your API key |
| Body encoding mismatch | Ensure the raw body is UTF-8 encoded |
Testing Locally
Use the webhook test endpoint to send a test event:
await tanqory.webhooks.test(storeId, webhookId, { event: 'order.created' })
For local development, use a tunnel service (e.g., ngrok) to expose your local endpoint:
ngrok http 3000
# Use the ngrok URL as your webhook endpoint