Skip to main content

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)
ParameterTypeDescription
rawBodystring or BufferThe raw request body (not parsed JSON)
signaturestringValue of the x-tanqory-signature header
secretstringWebhook 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​

MistakeFix
Parsing body before verificationUse express.raw() or req.text() to get the raw body
Using express.json() middleware globallyExclude the webhook route from JSON parsing
Wrong secretUse the secret from the webhook creation response, not your API key
Body encoding mismatchEnsure 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