Skip to main content

Webhooks

Webhooks provide real-time notifications when orders are updated, payments are processed, or other important events occur. This allows your application to stay synchronized with Torque without constantly polling the API.

Quick Start

Basic Webhook Setup

// Your webhook endpoint
app.post('/webhooks/torque', async (req, res) => {
try {
// Verify webhook signature
const signature = req.headers['x-torque-signature'];
const isValid = verifyWebhookSignature(req.body, signature, webhookSecret);

if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}

// Process the webhook
const { event, data } = req.body;

switch (event) {
case 'order.created':
await handleOrderCreated(data);
break;
case 'order.paid':
await handleOrderPaid(data);
break;
case 'order.completed':
await handleOrderCompleted(data);
break;
default:
console.log('Unhandled event:', event);
}

res.json({ success: true });
} catch (error) {
console.error('Webhook error:', error);
res.status(500).json({ error: 'Webhook processing failed' });
}
});

🔧 Webhook Configuration

Setting Up Webhooks

  1. Configure URL: Set your webhook URL in the Torque dashboard
  2. Choose Events: Select which events you want to receive
  3. Set Secret: Generate a webhook secret for signature verification
  4. Test: Send test webhooks to verify your endpoint

Webhook Settings

{
"url": "https://yourdomain.com/webhooks/torque",
"events": [
"order.created",
"order.paid",
"order.completed",
"order.cancelled",
"payment.failed"
],
"secret": "whsec_your_webhook_secret",
"active": true,
"retryCount": 3
}

📡 Webhook Events

Order Events

order.created

Triggered when a new order is created.

{
"event": "order.created",
"timestamp": "2024-01-15T10:00:00Z",
"data": {
"orderId": "order_abc123",
"sessionId": "session_abc123",
"businessId": "business_123",
"status": "pending",
"customerData": {
"email": "customer@example.com",
"firstName": "John",
"lastName": "Doe"
},
"cart": {
"items": [
{
"productId": "prod_1",
"quantity": 2,
"price": 29.99
}
],
"total": 59.98,
"currency": "USD"
},
"createdAt": "2024-01-15T10:00:00Z"
}
}

order.paid

Triggered when payment is received for an order.

{
"event": "order.paid",
"timestamp": "2024-01-15T10:05:00Z",
"data": {
"orderId": "order_abc123",
"status": "processing",
"payment": {
"method": "crypto",
"transactionId": "tx_123",
"amount": 59.98,
"currency": "USD",
"confirmedAt": "2024-01-15T10:05:00Z"
},
"updatedAt": "2024-01-15T10:05:00Z"
}
}

order.completed

Triggered when an order is successfully completed.

{
"event": "order.completed",
"timestamp": "2024-01-15T10:10:00Z",
"data": {
"orderId": "order_abc123",
"status": "completed",
"completedAt": "2024-01-15T10:10:00Z",
"updatedAt": "2024-01-15T10:10:00Z"
}
}

order.cancelled

Triggered when an order is cancelled.

{
"event": "order.cancelled",
"timestamp": "2024-01-15T10:15:00Z",
"data": {
"orderId": "order_abc123",
"status": "cancelled",
"cancelledAt": "2024-01-15T10:15:00Z",
"cancellationReason": "Customer request",
"updatedAt": "2024-01-15T10:15:00Z"
}
}

Payment Events

payment.failed

Triggered when a payment attempt fails.

{
"event": "payment.failed",
"timestamp": "2024-01-15T10:20:00Z",
"data": {
"orderId": "order_abc123",
"payment": {
"method": "crypto",
"amount": 59.98,
"currency": "USD",
"failureReason": "Insufficient funds",
"failedAt": "2024-01-15T10:20:00Z"
},
"updatedAt": "2024-01-15T10:20:00Z"
}
}

payment.refunded

Triggered when a payment is refunded.

{
"event": "payment.refunded",
"timestamp": "2024-01-15T10:25:00Z",
"data": {
"orderId": "order_abc123",
"payment": {
"method": "crypto",
"amount": 59.98,
"currency": "USD",
"refundReason": "Customer request",
"refundedAt": "2024-01-15T10:25:00Z"
},
"updatedAt": "2024-01-15T10:25:00Z"
}
}

Customer Events

customer.updated

Triggered when customer information is updated.

{
"event": "customer.updated",
"timestamp": "2024-01-15T10:30:00Z",
"data": {
"customerId": "customer_123",
"email": "customer@example.com",
"changes": {
"firstName": "John",
"lastName": "Smith"
},
"updatedAt": "2024-01-15T10:30:00Z"
}
}

🔐 Security

Webhook Signature Verification

All webhooks include a signature header for verification. Verify the signature to ensure the webhook came from Torque.

JavaScript/Node.js

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');

return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}

// Usage
app.post('/webhooks/torque', (req, res) => {
const signature = req.headers['x-torque-signature'];
const isValid = verifyWebhookSignature(req.body, signature, webhookSecret);

if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}

// Process webhook...
});

PHP

<?php

function verifyWebhookSignature($payload, $signature, $secret) {
$expectedSignature = hash_hmac('sha256', json_encode($payload), $secret);
return hash_equals($expectedSignature, $signature);
}

// Usage
$signature = $_SERVER['HTTP_X_TORQUE_SIGNATURE'] ?? '';
$isValid = verifyWebhookSignature($_POST, $signature, $webhookSecret);

if (!$isValid) {
http_response_code(401);
echo json_encode(['error' => 'Invalid signature']);
exit;
}

// Process webhook...
?>

Python

import hmac
import hashlib
import json

def verify_webhook_signature(payload, signature, secret):
expected_signature = hmac.new(
secret.encode('utf-8'),
json.dumps(payload, separators=(',', ':')).encode('utf-8'),
hashlib.sha256
).hexdigest()

return hmac.compare_digest(expected_signature, signature)

# Usage
@app.route('/webhooks/torque', methods=['POST'])
def webhook():
signature = request.headers.get('X-Torque-Signature', '')
payload = request.get_json()

if not verify_webhook_signature(payload, signature, webhook_secret):
return jsonify({'error': 'Invalid signature'}), 401

# Process webhook...
return jsonify({'success': True})

📝 Webhook Headers

Standard Headers

Content-Type: application/json
User-Agent: Torque-Webhooks/1.0
X-Torque-Signature: sha256=abc123...
X-Torque-Event: order.created
X-Torque-Delivery: delivery_abc123
X-Torque-Timestamp: 1642233600

Header Descriptions

  • X-Torque-Signature: HMAC SHA256 signature for verification
  • X-Torque-Event: Type of event being delivered
  • X-Torque-Delivery: Unique delivery identifier
  • X-Torque-Timestamp: Unix timestamp of the event

🔄 Retry Logic

Automatic Retries

Torque automatically retries failed webhook deliveries:

  • Retry Schedule: 1min, 5min, 15min, 1hour, 6hours, 24hours
  • Max Retries: 3 attempts (configurable)
  • Success Response: Return HTTP 200-299 status code

Handling Retries

app.post('/webhooks/torque', async (req, res) => {
try {
// Verify signature
const signature = req.headers['x-torque-signature'];
if (!verifyWebhookSignature(req.body, signature, webhookSecret)) {
return res.status(401).json({ error: 'Invalid signature' });
}

// Process webhook
await processWebhook(req.body);

// Return success
res.json({ success: true });
} catch (error) {
console.error('Webhook processing failed:', error);

// Return error to trigger retry
res.status(500).json({
error: 'Processing failed',
retryAfter: 60 // Retry in 60 seconds
});
}
});

📊 Webhook Management

List Webhooks

GET /v1/webhooks

const response = await fetch('https://api.torque.fi/v1/webhooks', {
headers: {
'Authorization': `Bearer ${apiKey}`
}
});

const webhooks = await response.json();

Create Webhook

POST /v1/webhooks

const response = await fetch('https://api.torque.fi/v1/webhooks', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
url: 'https://yourdomain.com/webhooks/torque',
events: ['order.created', 'order.paid'],
secret: 'your_webhook_secret'
})
});

const webhook = await response.json();

Update Webhook

PUT /v1/webhooks/{webhookId}

const response = await fetch(`https://api.torque.fi/v1/webhooks/${webhookId}`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
events: ['order.created', 'order.paid', 'order.completed']
})
});

const webhook = await response.json();

Delete Webhook

DELETE /v1/webhooks/{webhookId}

const response = await fetch(`https://api.torque.fi/v1/webhooks/${webhookId}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${apiKey}`
}
});

if (response.ok) {
console.log('Webhook deleted successfully');
}

Testing Webhooks

Test Endpoint

Use our test endpoint to verify your webhook setup:

curl -X POST https://api.torque.fi/v1/webhooks/test \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourdomain.com/webhooks/torque",
"event": "order.created"
}'

Local Testing

For local development, use tools like ngrok to expose your local server:

# Install ngrok
npm install -g ngrok

# Expose local server
ngrok http 3000

# Use the ngrok URL in your webhook configuration
# https://abc123.ngrok.io/webhooks/torque

Error Handling

Common Issues

Invalid Signature

{
"error": "Invalid webhook signature",
"code": "WEBHOOK_001"
}

Solutions:

  • Verify your webhook secret is correct
  • Check that the signature header is present
  • Ensure you're using the correct verification method

Webhook URL Unreachable

{
"error": "Webhook URL is unreachable",
"code": "WEBHOOK_002"
}

Solutions:

  • Check your webhook endpoint is accessible
  • Verify HTTPS is enabled
  • Test with a simple endpoint first

Processing Timeout

{
"error": "Webhook processing timeout",
"code": "WEBHOOK_003"
}

Solutions:

  • Process webhooks asynchronously
  • Return quickly and process in background
  • Use queue systems for heavy processing

📱 Complete Examples

Express.js Webhook Handler

const express = require('express');
const crypto = require('crypto');
const app = express();

app.use(express.json());

const webhookSecret = process.env.TORQUE_WEBHOOK_SECRET;

// Verify webhook signature
function verifySignature(payload, signature) {
const expectedSignature = crypto
.createHmac('sha256', webhookSecret)
.update(JSON.stringify(payload))
.digest('hex');

return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}

// Webhook endpoint
app.post('/webhooks/torque', async (req, res) => {
try {
// Verify signature
const signature = req.headers['x-torque-signature'];
if (!signature || !verifySignature(req.body, signature)) {
return res.status(401).json({ error: 'Invalid signature' });
}

const { event, data } = req.body;

// Process webhook based on event type
switch (event) {
case 'order.created':
await handleOrderCreated(data);
break;
case 'order.paid':
await handleOrderPaid(data);
break;
case 'order.completed':
await handleOrderCompleted(data);
break;
case 'order.cancelled':
await handleOrderCancelled(data);
break;
case 'payment.failed':
await handlePaymentFailed(data);
break;
default:
console.log('Unhandled event:', event);
}

res.json({ success: true });
} catch (error) {
console.error('Webhook processing error:', error);
res.status(500).json({ error: 'Processing failed' });
}
});

// Event handlers
async function handleOrderCreated(data) {
console.log('New order created:', data.orderId);
// Update your database, send notifications, etc.
}

async function handleOrderPaid(data) {
console.log('Order paid:', data.orderId);
// Process payment, update inventory, etc.
}

async function handleOrderCompleted(data) {
console.log('Order completed:', data.orderId);
// Send confirmation emails, update analytics, etc.
}

async function handleOrderCancelled(data) {
console.log('Order cancelled:', data.orderId);
// Restore inventory, send cancellation emails, etc.
}

async function handlePaymentFailed(data) {
console.log('Payment failed:', data.orderId);
// Send failure notifications, retry logic, etc.
}

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Webhook server running on port ${PORT}`);
});

Laravel Webhook Handler

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;

class TorqueWebhookController extends Controller
{
public function handle(Request $request)
{
try {
// Verify signature
$signature = $request->header('X-Torque-Signature');
if (!$this->verifySignature($request->getContent(), $signature)) {
return response()->json(['error' => 'Invalid signature'], 401);
}

$data = $request->all();
$event = $data['event'] ?? '';

// Process webhook based on event type
switch ($event) {
case 'order.created':
$this->handleOrderCreated($data['data']);
break;
case 'order.paid':
$this->handleOrderPaid($data['data']);
break;
case 'order.completed':
$this->handleOrderCompleted($data['data']);
break;
case 'order.cancelled':
$this->handleOrderCancelled($data['data']);
break;
case 'payment.failed':
$this->handlePaymentFailed($data['data']);
break;
default:
Log::info('Unhandled webhook event', ['event' => $event]);
}

return response()->json(['success' => true]);
} catch (\Exception $e) {
Log::error('Webhook processing error', ['error' => $e->getMessage()]);
return response()->json(['error' => 'Processing failed'], 500);
}
}

private function verifySignature($payload, $signature)
{
$expectedSignature = hash_hmac('sha256', $payload, config('torque.webhook_secret'));
return hash_equals($expectedSignature, $signature);
}

private function handleOrderCreated($data)
{
Log::info('New order created', ['orderId' => $data['orderId']]);
// Update database, send notifications, etc.
}

private function handleOrderPaid($data)
{
Log::info('Order paid', ['orderId' => $data['orderId']]);
// Process payment, update inventory, etc.
}

private function handleOrderCompleted($data)
{
Log::info('Order completed', ['orderId' => $data['orderId']]);
// Send confirmation emails, update analytics, etc.
}

private function handleOrderCancelled($data)
{
Log::info('Order cancelled', ['orderId' => $data['orderId']]);
// Restore inventory, send cancellation emails, etc.
}

private function handlePaymentFailed($data)
{
Log::info('Payment failed', ['orderId' => $data['orderId']]);
// Send failure notifications, retry logic, etc.
}
}

Next Steps


Need help with webhooks? Check our webhook troubleshooting guide or contact support at hello@torque.fi.