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
- Configure URL: Set your webhook URL in the Torque dashboard
- Choose Events: Select which events you want to receive
- Set Secret: Generate a webhook secret for signature verification
- 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 verificationX-Torque-Event
: Type of event being deliveredX-Torque-Delivery
: Unique delivery identifierX-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
- Analytics API: Track business performance and webhook delivery
- Error Handling: Handle webhook errors and failures
- Business Dashboard: Manage webhook settings
- Platform Integrations: Set up webhooks for specific platforms
Need help with webhooks? Check our webhook troubleshooting guide or contact support at hello@torque.fi.