Webhook API Documentation
Connect TradesOps to your favorite tools and workflows. Receive real-time notifications when events happen in your account -- invoices paid, quotes accepted, projects completed, and more.
Getting Started
TradesOps webhooks let you subscribe to events that happen in your organization. When an event occurs (e.g., an invoice is paid), we send an HTTP POST request to your configured URL with the event details.
Quick start in 3 steps:
- 1Register a webhook endpoint -- Go to
Settings > Webhooksin your dashboard, or use thePOST /api/webhooksendpoint. - 2Choose your events -- Select which events you want to receive. You can subscribe to all events or pick specific ones.
- 3Save your secret -- When you create a webhook, you will receive a signing secret. Store it securely -- you will need it to verify that incoming requests are genuinely from TradesOps.
Important
Your webhook secret is only shown once when the webhook is created. Make sure to copy and store it securely. If you lose it, you will need to delete the webhook and create a new one.
Authentication
Webhook management endpoints require session authentication (you must be logged in). All webhook API calls are scoped to your organization -- you can only manage webhooks that belong to your org.
API Endpoints
Create Webhook Request
{
"name": "My Integration",
"url": "https://example.com/webhooks/platform",
"events": [
"invoice.paid",
"quote.accepted",
"project.completed"
],
"isActive": true
}The response will include the webhook object with a secret field. This is the only time the secret is returned.
Payload Format
Every webhook delivery uses the same envelope format. The payload is sent as a JSON POST request with Content-Type: application/json.
{
"event": "invoice.paid",
"data": {
"id": "clx1abc2300001",
"invoiceNumber": "INV-0042",
"clientName": "Acme Construction LLC",
"total": 12500,
"amountPaid": 12500,
"currency": "USD",
"status": "PAID",
"paidAt": "2026-03-20T09:15:00.000Z",
"paymentMethod": "stripe"
},
"timestamp": "2026-03-20T09:15:01.234Z",
"webhookId": "clxwh001",
"attempt": 1
}| Field | Type | Description |
|---|---|---|
| event | string | The event type (e.g., invoice.paid) |
| data | object | Event-specific payload data (see event reference below) |
| timestamp | string | ISO 8601 timestamp of when the event was fired |
| webhookId | string | The ID of the webhook subscription that triggered this delivery |
| attempt | number | Delivery attempt number (1 = first attempt, 2+ = retries) |
HTTP Headers
Every webhook delivery includes these headers:
| Header | Description |
|---|---|
| X-Webhook-Signature | HMAC-SHA256 signature of the raw request body: sha256=<hex> |
| X-Webhook-Event | The event type (same as the event field in the body) |
| X-Webhook-Id | The webhook subscription ID |
| X-Webhook-Attempt | Current delivery attempt number |
| Content-Type | Always application/json |
Webhook Events
TradesOps supports 57 webhook events across 23 categories. Each event includes a data field with the relevant entity details.
Quick Reference
Invoices
invoice.createdFired when a new invoice is created in the system.
Example data payload
{
"id": "clx1abc2300001",
"invoiceNumber": "INV-0042",
"clientName": "Acme Construction LLC",
"contactId": "clx1abc2300002",
"projectId": "clx1abc2300003",
"total": 12500,
"currency": "USD",
"status": "DRAFT",
"dueDate": "2026-04-15T00:00:00.000Z",
"lineItems": [
{
"description": "Foundation pour - 200 sq ft",
"quantity": 200,
"unitPrice": 45,
"total": 9000
},
{
"description": "Rebar installation",
"quantity": 1,
"unitPrice": 3500,
"total": 3500
}
]
}invoice.sentFired when an invoice is sent to the client via email.
Example data payload
{
"id": "clx1abc2300001",
"invoiceNumber": "INV-0042",
"clientName": "Acme Construction LLC",
"contactId": "clx1abc2300002",
"total": 12500,
"currency": "USD",
"status": "SENT",
"sentAt": "2026-03-18T14:30:00.000Z",
"recipientEmail": "billing@acme-construction.com"
}invoice.paidFired when an invoice is fully paid (payment received and matched).
Example data payload
{
"id": "clx1abc2300001",
"invoiceNumber": "INV-0042",
"clientName": "Acme Construction LLC",
"total": 12500,
"amountPaid": 12500,
"currency": "USD",
"status": "PAID",
"paidAt": "2026-03-20T09:15:00.000Z",
"paymentMethod": "stripe"
}invoice.overdueFired when an invoice passes its due date without full payment.
Example data payload
{
"id": "clx1abc2300001",
"invoiceNumber": "INV-0042",
"clientName": "Acme Construction LLC",
"total": 12500,
"amountPaid": 0,
"currency": "USD",
"status": "OVERDUE",
"dueDate": "2026-03-15T00:00:00.000Z",
"daysOverdue": 3
}Quotes
quote.createdFired when a new quote/estimate is created.
Example data payload
{
"id": "clx2def4500001",
"quoteNumber": "QTE-0018",
"clientName": "Pacific Builders Inc",
"contactId": "clx2def4500002",
"total": 28750,
"currency": "USD",
"status": "DRAFT",
"validUntil": "2026-04-17T00:00:00.000Z"
}quote.sentFired when a quote is sent to the client for review.
Example data payload
{
"id": "clx2def4500001",
"quoteNumber": "QTE-0018",
"clientName": "Pacific Builders Inc",
"total": 28750,
"currency": "USD",
"status": "SENT",
"sentAt": "2026-03-18T10:00:00.000Z"
}quote.acceptedFired when a client accepts a quote.
Example data payload
{
"id": "clx2def4500001",
"quoteNumber": "QTE-0018",
"clientName": "Pacific Builders Inc",
"total": 28750,
"currency": "USD",
"status": "ACCEPTED",
"acceptedAt": "2026-03-19T16:45:00.000Z"
}quote.rejectedFired when a client rejects a quote.
Example data payload
{
"id": "clx2def4500001",
"quoteNumber": "QTE-0018",
"clientName": "Pacific Builders Inc",
"total": 28750,
"currency": "USD",
"status": "REJECTED",
"rejectedAt": "2026-03-19T16:45:00.000Z",
"reason": "Budget constraints"
}quote.signedFired when a client signs a quote via the e-sign link.
Example data payload
{
"id": "clx002",
"quoteNumber": "QTE-0020",
"signerName": "John Doe",
"signedAt": "2026-03-18T14:00:00.000Z"
}Projects
project.createdFired when a new project is created.
Example data payload
{
"id": "clx3ghi6700001",
"name": "Downtown Office Foundation",
"clientName": "Acme Construction LLC",
"contactId": "clx1abc2300002",
"status": "PLANNING",
"startDate": "2026-04-01T00:00:00.000Z",
"estimatedEndDate": "2026-06-30T00:00:00.000Z",
"budget": 150000,
"currency": "USD"
}project.status_changedFired when a project transitions to a new status (e.g., Planning to In Progress).
Example data payload
{
"id": "clx3ghi6700001",
"name": "Downtown Office Foundation",
"previousStatus": "PLANNING",
"newStatus": "IN_PROGRESS",
"changedAt": "2026-04-01T08:00:00.000Z",
"changedBy": "John Smith"
}project.completedFired when a project is marked as completed.
Example data payload
{
"id": "clx3ghi6700001",
"name": "Downtown Office Foundation",
"status": "COMPLETED",
"completedAt": "2026-06-28T17:00:00.000Z",
"totalCost": 142500,
"budget": 150000,
"currency": "USD"
}project.budget_alertFired when project costs exceed alert thresholds.
Example data payload
{
"projectId": "clx015",
"alertType": "budget_exceeded",
"threshold": 80,
"currentValue": 85
}Contacts
contact.createdFired when a new contact (lead or client) is added.
Example data payload
{
"id": "clx4jkl8900001",
"name": "Jane Doe",
"email": "jane@example.com",
"phone": "+1-555-0123",
"company": "Pacific Builders Inc",
"stage": "LEAD",
"source": "website"
}contact.stage_changedFired when a contact moves through the CRM pipeline (e.g., Lead to Qualified).
Example data payload
{
"id": "clx4jkl8900001",
"name": "Jane Doe",
"previousStage": "LEAD",
"newStage": "QUALIFIED",
"changedAt": "2026-03-18T11:30:00.000Z"
}contact.credit_checkedFired when a credit check is performed on a contact.
Example data payload
{
"contactId": "clx016",
"score": 720,
"riskLevel": "low"
}Payments
payment.receivedFired when a payment is recorded against an invoice.
Example data payload
{
"id": "clx5mno0100001",
"invoiceId": "clx1abc2300001",
"invoiceNumber": "INV-0042",
"amount": 12500,
"currency": "USD",
"method": "stripe",
"receivedAt": "2026-03-20T09:15:00.000Z",
"clientName": "Acme Construction LLC"
}milestone.invoicedFired when a payment milestone generates an invoice.
Example data payload
{
"milestoneId": "clx007",
"invoiceId": "clx008",
"amount": 25000
}Contracts
contract.signedFired when a contract is signed by the client (via the public signing page).
Example data payload
{
"id": "clx6pqr2300001",
"contractNumber": "CTR-0007",
"clientName": "Acme Construction LLC",
"contactId": "clx1abc2300002",
"projectId": "clx3ghi6700001",
"total": 150000,
"currency": "USD",
"status": "SIGNED",
"signedAt": "2026-03-18T14:00:00.000Z",
"signerName": "Bob Johnson",
"signerEmail": "bob@acme-construction.com"
}Expenses
expense.createdFired when a new expense is recorded.
Example data payload
{
"id": "clx7stu4500001",
"description": "Concrete delivery - 20 yards",
"amount": 2800,
"currency": "USD",
"category": "Materials",
"projectId": "clx3ghi6700001",
"projectName": "Downtown Office Foundation",
"vendor": "Ready Mix Co",
"date": "2026-03-18T00:00:00.000Z"
}Scheduling
schedule.createdFired when a new schedule entry is created for a project.
Example data payload
{
"id": "clx8vwx6700001",
"title": "Foundation Pour - Phase 1",
"projectId": "clx3ghi6700001",
"projectName": "Downtown Office Foundation",
"startDate": "2026-04-05T07:00:00.000Z",
"endDate": "2026-04-05T16:00:00.000Z",
"assignedTo": [
"John Smith",
"Mike Davis"
]
}Equipment
equipment.createdFired when a new equipment item is added to the inventory.
Example data payload
{
"id": "clx9yza8900001",
"name": "CAT 320 Excavator",
"type": "Excavator",
"status": "AVAILABLE",
"serialNumber": "CAT320-2024-001",
"purchaseDate": "2024-06-15T00:00:00.000Z",
"purchasePrice": 185000
}equipment.updatedFired when equipment details or status are updated.
Example data payload
{
"id": "clx9yza8900001",
"name": "CAT 320 Excavator",
"status": "IN_USE",
"changes": {
"status": {
"from": "AVAILABLE",
"to": "IN_USE"
}
}
}equipment.deletedFired when an equipment item is removed from the system.
Example data payload
{
"id": "clx9yza8900001",
"name": "CAT 320 Excavator",
"deletedAt": "2026-03-18T12:00:00.000Z"
}Vendors
vendor.createdFired when a new vendor/supplier is added.
Example data payload
{
"id": "clxabcd0100001",
"name": "Ready Mix Co",
"email": "orders@readymix.com",
"phone": "+1-555-0456",
"category": "Concrete Supplier"
}vendor.updatedFired when vendor details are updated.
Example data payload
{
"id": "clxabcd0100001",
"name": "Ready Mix Co",
"changes": {
"phone": {
"from": "+1-555-0456",
"to": "+1-555-0789"
}
}
}vendor.deletedFired when a vendor is removed from the system.
Example data payload
{
"id": "clxabcd0100001",
"name": "Ready Mix Co",
"deletedAt": "2026-03-18T12:00:00.000Z"
}Incidents
incident.createdFired when a safety incident or report is created.
Example data payload
{
"id": "clxbefg2300001",
"title": "Minor equipment malfunction",
"severity": "LOW",
"projectId": "clx3ghi6700001",
"projectName": "Downtown Office Foundation",
"reportedBy": "Mike Davis",
"reportedAt": "2026-03-18T10:30:00.000Z"
}incident.updatedFired when an incident report is updated (e.g., resolution added).
Example data payload
{
"id": "clxbefg2300001",
"title": "Minor equipment malfunction",
"severity": "LOW",
"status": "RESOLVED",
"resolvedAt": "2026-03-18T15:00:00.000Z"
}incident.deletedFired when an incident report is deleted.
Example data payload
{
"id": "clxbefg2300001",
"title": "Minor equipment malfunction",
"deletedAt": "2026-03-18T12:00:00.000Z"
}Permits
permit.createdFired when a new permit is added to a project.
Example data payload
{
"id": "clxchij4500001",
"permitNumber": "BP-2026-0042",
"type": "Building Permit",
"projectId": "clx3ghi6700001",
"projectName": "Downtown Office Foundation",
"status": "PENDING",
"issuedBy": "City of Portland",
"expiresAt": "2027-03-18T00:00:00.000Z"
}permit.updatedFired when a permit status or details are updated.
Example data payload
{
"id": "clxchij4500001",
"permitNumber": "BP-2026-0042",
"status": "APPROVED",
"approvedAt": "2026-03-20T09:00:00.000Z"
}permit.deletedFired when a permit is removed from the system.
Example data payload
{
"id": "clxchij4500001",
"permitNumber": "BP-2026-0042",
"deletedAt": "2026-03-18T12:00:00.000Z"
}Estimates
estimate.createdFired when a new cost estimate is created.
Example data payload
{
"id": "clxdklm6700001",
"name": "Foundation Work Estimate",
"projectId": "clx3ghi6700001",
"total": 45000,
"currency": "USD",
"status": "DRAFT"
}estimate.updatedFired when an estimate is revised or updated.
Example data payload
{
"id": "clxdklm6700001",
"name": "Foundation Work Estimate",
"total": 47500,
"currency": "USD",
"status": "REVISED",
"previousTotal": 45000
}Timesheets
timesheet.status_changedFired when a timesheet is submitted, approved, or rejected.
Example data payload
{
"id": "clxenop8900001",
"employeeName": "John Smith",
"employeeId": "clxenop8900002",
"weekStarting": "2026-03-16T00:00:00.000Z",
"totalHours": 42.5,
"previousStatus": "SUBMITTED",
"newStatus": "APPROVED",
"approvedBy": "Jane Manager"
}Daily Logs
daily_log.createdFired when a daily site log is created for a project.
Example data payload
{
"id": "clxfqrs0100001",
"projectId": "clx3ghi6700001",
"projectName": "Downtown Office Foundation",
"date": "2026-03-18T00:00:00.000Z",
"weather": "Clear, 72F",
"crewSize": 8,
"summary": "Completed foundation forms for section B. Rebar inspection passed."
}daily_log.updatedFired when a daily log entry is updated.
Example data payload
{
"id": "clxfqrs0100001",
"projectId": "clx3ghi6700001",
"date": "2026-03-18T00:00:00.000Z",
"updatedFields": [
"summary",
"crewSize"
]
}Purchase Orders
purchase_order.sentFired when a purchase order is sent to a vendor.
Example data payload
{
"id": "clxgtuv2300001",
"poNumber": "PO-0015",
"vendorName": "Ready Mix Co",
"vendorId": "clxabcd0100001",
"projectId": "clx3ghi6700001",
"total": 5600,
"currency": "USD",
"status": "SENT",
"sentAt": "2026-03-18T09:00:00.000Z"
}Settings
tax_rate.createdFired when a new tax rate is created.
Example data payload
{
"id": "clx001",
"name": "State Sales Tax",
"rate": 0.0825
}tax_rate.updatedFired when a tax rate is modified.
Example data payload
{
"id": "clx001",
"name": "State Sales Tax",
"rate": 0.09
}import.completedFired when a data import finishes processing.
Example data payload
{
"id": "clx010",
"source": "csv",
"entityType": "contacts",
"totalRows": 500,
"processedRows": 498,
"failedRows": 2
}Bookings
booking.createdFired when a new booking is created through the public page.
Example data payload
{
"id": "clx003",
"name": "Jane Smith",
"email": "jane@example.com",
"date": "2026-04-01",
"startTime": "09:00"
}booking.confirmedFired when a booking is confirmed.
Example data payload
{
"id": "clx003",
"status": "confirmed"
}booking.cancelledFired when a booking is cancelled.
Example data payload
{
"id": "clx003",
"status": "cancelled"
}Portal
portal.quote_approvedFired when a client approves a quote through the portal.
Example data payload
{
"quoteId": "clx004",
"approvedBy": "Client User"
}portal.quote_rejectedFired when a client rejects a quote through the portal.
Example data payload
{
"quoteId": "clx004",
"rejectedBy": "Client User",
"reason": "Too expensive"
}portal.change_order_approvedFired when a client approves a change order through the portal.
Example data payload
{
"changeOrderId": "clx005",
"approvedBy": "Client User"
}portal.message_sentFired when a client sends a message through the portal.
Example data payload
{
"messageId": "clx006",
"content": "Question about timeline"
}Inventory
inventory.low_stockFired when an inventory item falls below reorder point.
Example data payload
{
"id": "clx009",
"name": "Rebar #4",
"currentStock": 50,
"reorderPoint": 100
}RFQs
rfq.createdFired when a new RFQ is created.
Example data payload
{
"id": "clx011",
"rfqNumber": "RFQ-0001",
"title": "Concrete Materials"
}rfq.response_receivedFired when a vendor submits an RFQ response.
Example data payload
{
"rfqId": "clx011",
"vendorId": "clx012",
"totalAmount": 15000
}rfq.awardedFired when an RFQ is awarded to a vendor.
Example data payload
{
"rfqId": "clx011",
"vendorId": "clx012",
"poId": "clx013"
}Warranties
warranty_claim.submittedFired when a client submits a warranty claim.
Example data payload
{
"claimId": "clx014",
"title": "Crack in foundation"
}warranty_claim.resolvedFired when a warranty claim is resolved.
Example data payload
{
"claimId": "clx014",
"resolution": "Repaired"
}Meetings
meeting.createdFired when a new meeting note is created.
Example data payload
{
"id": "clx017",
"title": "Client kickoff",
"projectId": "clx015"
}meeting.transcribedFired when a meeting recording is transcribed.
Example data payload
{
"id": "clx017",
"duration": 3600
}Signature Verification
Every webhook delivery is signed with HMAC-SHA256 using your webhook's secret. The signature is included in the X-Webhook-Signature header as sha256=<hex_digest>.
You should always verify the signature to ensure the request genuinely came fromTradesOps and has not been tampered with.
How signature verification works:
- Read the raw request body as a string (do not parse it first)
- Compute the HMAC-SHA256 digest of the raw body using your webhook secret
- Compare the computed digest with the value in the
X-Webhook-Signatureheader (after removing thesha256=prefix) - Use a timing-safe comparison function to prevent timing attacks
Node.js / Express
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
const received = signature.replace('sha256=', '');
// Use timing-safe comparison to prevent timing attacks
return crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(received, 'hex')
);
}
// Express.js example
app.post('/webhooks/platform', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const rawBody = JSON.stringify(req.body);
if (!verifyWebhookSignature(rawBody, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const { event, data } = req.body;
console.log(`Received event: ${event}`, data);
// Process the event...
res.status(200).send('OK');
});Python / Flask
import hmac
import hashlib
from flask import Flask, request, jsonify
app = Flask(__name__)
WEBHOOK_SECRET = 'your_webhook_secret_here'
def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode('utf-8'),
payload,
hashlib.sha256
).hexdigest()
received = signature.replace('sha256=', '')
return hmac.compare_digest(expected, received)
@app.route('/webhooks/platform', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Webhook-Signature', '')
raw_body = request.get_data()
if not verify_signature(raw_body, signature, WEBHOOK_SECRET):
return jsonify({'error': 'Invalid signature'}), 401
data = request.get_json()
event = data.get('event')
print(f'Received event: {event}')
# Process the event...
return jsonify({'status': 'ok'}), 200PHP
<?php
$secret = getenv('WEBHOOK_SECRET');
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $payload, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
echo json_encode(['error' => 'Invalid signature']);
exit;
}
$data = json_decode($payload, true);
$event = $data['event'];
// Process the event...
error_log("Received webhook event: $event");
http_response_code(200);
echo json_encode(['status' => 'ok']);Security Warning
Always verify signatures in production. Never skip verification, even for testing. Use a timing-safe comparison (e.g., crypto.timingSafeEqual in Node.js, hmac.compare_digest in Python, or hash_equals in PHP) to prevent timing attacks.
Retry Policy
If your endpoint returns a non-2xx status code or the request times out,TradesOps will automatically retry the delivery with exponential backoff.
| Attempt | Delay | Notes |
|---|---|---|
| 1 (initial) | Immediate | First delivery attempt |
| 2 (retry 1) | 5 seconds | 5,000 ms * 5^0 |
| 3 (retry 2) | 25 seconds | 5,000 ms * 5^1 |
| 4 (retry 3) | ~2 minutes | 5,000 ms * 5^2 = 125 seconds |
Request Timeout
Each delivery attempt has a 10-second timeout. If your endpoint does not respond within 10 seconds, it is treated as a failure.
Auto-Disable
After 10 consecutive failures (across any events), the webhook is automatically disabled. You can re-enable it from the settings page.
What counts as a successful delivery?
Any HTTP response with a 2xx status code (200-299) is considered successful. The response body is logged but not validated -- you can return whatever you want. If you return a non-2xx status or the connection fails, the delivery will be retried.
Testing Webhooks
TradesOps provides several ways to test your webhook integration before going live.
Test from Dashboard
Each webhook in Settings > Webhooks has a test button that sends a test.ping event to your endpoint. The delivery result (status code, response time, response body) is shown immediately.
Test API Endpoint
Use the POST /api/webhooks/test endpoint to send a test ping to any URL without creating a persistent webhook:
{
"url": "https://your-endpoint.com/webhook",
"secret": "optional_secret_for_signing"
}If you omit the secret field, a temporary secret will be generated and returned in the response.
Test Ping Payload
The test.ping event looks like this:
{
"event": "test.ping",
"data": {
"message": "This is a test webhook from TradesOps. If you receive this, your endpoint is working correctly.",
"testId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"timestamp": "2026-03-18T14:30:00.000Z"
},
"timestamp": "2026-03-18T14:30:00.000Z",
"webhookId": "test_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"attempt": 1
}Recommended Testing Tools
webhook.site
Free tool that gives you a unique URL to receive and inspect webhook payloads. Great for debugging.
webhook.sitengrok
Exposes your local development server to the internet. Perfect for testing webhooks against your local code.
ngrok.comZapier Integration
Connect TradesOps to 6,000+ apps using Zapier. Use TradesOps webhooks as a trigger to automate workflows -- send Slack messages when invoices are paid, create Google Sheets rows for new quotes, update your CRM when projects are completed, and more.
Step-by-step setup:
- 1
Create a Zap
In Zapier, click "Create Zap" and search for "Webhooks by Zapier" as the trigger app. Choose "Catch Hook" as the trigger event.
- 2
Copy the Zapier webhook URL
Zapier will give you a unique URL like
https://hooks.zapier.com/hooks/catch/123456/abcdef/. Copy this URL. - 3
Register in TradesOps
Go to Settings > Webhooks in your TradesOps dashboard. Click "Add Webhook", paste the Zapier URL, give it a name (e.g., "Zapier - Invoice Notifications"), and select the events you want to receive.
- 4
Send a test event
Click the test button (paper plane icon) next to your webhook in TradesOps. Then go back to Zapier and click "Test trigger" -- it should find the test event.
- 5
Add an action
Choose what happens when the webhook fires. For example: send a Slack message, create a Google Sheets row, send an email, update HubSpot, etc. Map the webhook data fields to your action fields.
- 6
Turn on your Zap
Test the full workflow, then publish your Zap. It will now run automatically whenever the subscribed events fire in TradesOps.
Zapier Tips
Filter by event type
If you subscribe to multiple events, add a Zapier Filter step after the trigger. Filter on event equals invoice.paid (or whichever event you want that specific Zap to handle).
Access nested data
Webhook data is nested under data. In Zapier's field mapping, look for fields like data__invoiceNumber or data__total.
Create separate Zaps for each event
For cleaner automation, create a separate webhook endpoint in TradesOps for each workflow. E.g., one webhook for "Slack notifications on invoice.paid" and another for "Google Sheets log on quote.accepted".
Make (Integromat) Integration
Make (formerly Integromat) is a powerful visual automation platform. Use TradesOpswebhooks to trigger Make scenarios and connect with hundreds of apps.
Step-by-step setup:
- 1
Create a new scenario
In Make, create a new scenario. Click the "+" button to add a module and search for "Webhooks". Select "Custom webhook".
- 2
Create and copy the webhook URL
Click "Add" to create a new webhook in Make. Give it a name (e.g., "TradesOps Events") and copy the generated URL. It will look like
https://hook.make.com/abc123xyz.... - 3
Register in TradesOps
Go to Settings > Webhooks in your dashboard. Create a new webhook with the Make URL and select your desired events.
- 4
Determine the data structure
Back in Make, click "Re-determine data structure" (or it may wait automatically). Then send a test event from TradesOps. Make will parse the incoming payload and learn its structure.
- 5
Add a Router (optional)
If you subscribe to multiple events, add a Router module after the webhook. Create separate routes with filters like
eventequalsinvoice.paid. Each route can trigger different actions. - 6
Add action modules
Add modules for your desired actions: send emails, update spreadsheets, create records in other apps, etc. Map the webhook data fields to each module's inputs.
- 7
Activate the scenario
Test the scenario with the sample data, then toggle it to "ON". Make will now process incoming webhooks in real time.
Make Tips
Verify signatures in Make
Make supports webhook signature verification. In the webhook module settings, enable "Webhook verification" and configure HMAC-SHA256 with your TradesOpswebhook secret. Set the header name to X-Webhook-Signature and the prefix to sha256=.
Handle retries gracefully
Make processes each webhook delivery independently. If TradesOps retries a failed delivery, Make will process it as a new execution. Check the attempt field in the payload if you need to detect retries.
Use data stores for deduplication
If idempotency is important, use a Make Data Store to track processed event IDs. Check the webhookId + event + timestamp combination to detect duplicates.
Best Practices
Respond quickly
Return a 200 response as soon as possible. Process the webhook asynchronously (e.g., add to a queue) rather than blocking the response. TradesOps has a 10-second timeout per delivery.
Handle duplicates
Due to retries, you may receive the same event more than once. Design your handler to be idempotent -- processing the same event twice should produce the same result.
Verify signatures
Always verify the X-Webhook-Signature header in production to ensure requests come from TradesOps.
Use HTTPS endpoints
Always use HTTPS for your webhook endpoint in production. This ensures the payload (including your data) is encrypted in transit.
Monitor delivery logs
Check the delivery logs in Settings > Webhooks regularly. If you see failures, investigate and fix them before the webhook is auto-disabled.
Subscribe selectively
Only subscribe to the events you need. This reduces unnecessary traffic and processing on your end.