Webhooks
Get real-time notifications of events in your Tageur account.
On this page:
1. Overview
Webhooks enable real-time integrations between Tageur and your external systems. When events occur in your Tageur account (like an asset being created or a task being completed), we'll automatically send HTTP POST requests to your specified endpoint URL.
Use Cases:
- Sync asset data with your ERP system (SAP, Dynamics, Odoo, etc.)
- Trigger automated workflows in your CMMS
- Update inventory management systems in real-time
- Send notifications to Slack, Teams, or custom applications
- Maintain audit trails in external compliance systems
2. Setting Up Webhooks
To create a webhook subscription, navigate to your company settings and configure:
- 1. Endpoint URL: The HTTPS URL where we'll send webhook events
- 2. Events: Select which event types to subscribe to
- 3. Filters (Optional): Filter events by specific criteria (e.g., only certain asset types)
- 4. Timeout & Retries: Configure timeout duration (1-60 seconds) and retry attempts (0-10)
Once created, you'll receive a webhook secret used to verify the authenticity of incoming requests.
3. Available Events
Subscribe to any combination of the following event types:
Asset Events
-
asset.createdTriggered when a new asset is created -
asset.updatedTriggered when asset details are modified -
asset.deletedTriggered when an asset is deleted
Part Events
-
part.createdTriggered when a new part is added to inventory -
part.updatedTriggered when part information is updated -
part.deletedTriggered when a part is removed from inventory
Task Events
-
task.createdTriggered when a new task is created -
task.updatedTriggered when task details are modified -
task.completedTriggered when a task is marked as completed -
task.comment.createdTriggered when a comment is added to a task
Shipment Events
Growth+-
shipment.createdTriggered when a new shipment is created -
shipment.updatedTriggered when shipment details are modified -
shipment.deletedTriggered when a shipment is deleted -
shipment.shippedTriggered when a shipment is marked as shipped -
shipment.deliveredTriggered when a shipment is marked as delivered
4. Payload Structure
All webhook payloads follow a consistent JSON structure with event metadata and resource data.
Asset Event Payload
{
"event": "asset.created",
"timestamp": "2025-01-04T12:34:56Z",
"data": {
"id": 123,
"type": "asset",
"attributes": {
"name": "Hydraulic Pump #5",
"asset_type": "Equipment",
"status": "operational",
"serial_number": "HP-2025-0123",
"brand": "Atlas Copco",
"model_number": "GA55",
"location": "Building A - Floor 2",
"company_office_location_id": 45,
"company_office_location": {
"id": 45,
"name": "Main Warehouse",
"city": "Chicago",
"state": "IL",
"is_primary": true
},
"purchase_date": "2024-12-01",
"purchase_price": "15000.00",
"extra_values": {
"department": "Manufacturing",
"cost_center": "CC-100"
},
"created_at": "2025-01-04T12:34:56Z",
"updated_at": "2025-01-04T12:34:56Z"
}
}
}
Part Event Payload
{
"event": "part.updated",
"timestamp": "2025-01-04T12:35:00Z",
"data": {
"id": 456,
"type": "part",
"attributes": {
"part_number": "BRG-2025-001",
"name": "Ball Bearing",
"description": "High-precision ball bearing for pump motors",
"category": "Bearings",
"status": "in_stock",
"vendor": "SKF",
"manufacturer": "SKF Group",
"unit_cost": "45.99",
"created_at": "2024-11-15T10:20:00Z",
"updated_at": "2025-01-04T12:35:00Z"
}
}
}
Task Event Payload
{
"event": "task.completed",
"timestamp": "2025-01-04T12:40:00Z",
"data": {
"id": 789,
"type": "task",
"attributes": {
"title": "Monthly maintenance - Pump #5",
"description": "Inspect and service hydraulic pump",
"status": "completed",
"priority": "high",
"task_type": "maintenance",
"due_date": "2025-01-04",
"asset_id": 123,
"asset_name": "Hydraulic Pump #5",
"assigned_to_id": 456,
"assigned_to_name": "John Smith",
"created_by_id": 100,
"created_by_name": "Jane Manager",
"estimated_hours": 2.5,
"actual_hours": 2.0,
"started_at": "2025-01-04T10:00:00Z",
"completed_at": "2025-01-04T12:40:00Z",
"comments_count": 3,
"created_at": "2025-01-01T09:00:00Z",
"updated_at": "2025-01-04T12:40:00Z"
}
}
}
Task Comment Event Payload
{
"event": "task.comment.created",
"timestamp": "2025-01-04T11:15:00Z",
"data": {
"id": 456,
"type": "comment",
"attributes": {
"content": "Hydraulic fluid levels checked and topped up. Pressure readings normal at 2500 PSI.",
"task_id": 789,
"task_title": "Monthly maintenance - Pump #5",
"user_id": 456,
"user_name": "John Smith",
"created_at": "2025-01-04T11:15:00Z",
"updated_at": "2025-01-04T11:15:00Z"
}
}
}
5. Security & Verification
Every webhook request includes an X-Tageur-Signature header containing an HMAC SHA-256 signature. Verify this signature to ensure requests are authentic.
Signature Verification
The signature is computed using your webhook secret and the request body:
Node.js / Express Example:
const crypto = require('crypto');
function verifyWebhookSignature(req, webhookSecret) {
const signature = req.headers['x-tageur-signature'];
const payload = JSON.stringify(req.body);
const expectedSignature = crypto
.createHmac('sha256', webhookSecret)
.update(payload)
.digest('hex');
return signature === expectedSignature;
}
// Express route handler
app.post('/webhooks/tageur', (req, res) => {
if (!verifyWebhookSignature(req, process.env.TAGEUR_WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process webhook event
const { event, data } = req.body;
console.log(\`Received event: \${event}\`, data);
res.status(200).json({ received: true });
});
Python / Flask Example:
import hmac
import hashlib
from flask import Flask, request, jsonify
app = Flask(__name__)
def verify_webhook_signature(request, webhook_secret):
signature = request.headers.get('X-Tageur-Signature')
payload = request.get_data()
expected_signature = hmac.new(
webhook_secret.encode('utf-8'),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected_signature)
@app.route('/webhooks/tageur', methods=['POST'])
def handle_webhook():
if not verify_webhook_signature(request, os.environ['TAGEUR_WEBHOOK_SECRET']):
return jsonify({'error': 'Invalid signature'}), 401
event_data = request.json
event_type = event_data.get('event')
data = event_data.get('data')
print(f"Received event: {event_type}", data)
return jsonify({'received': True}), 200
Security Best Practices:
- Always verify the signature before processing webhook data
- Use HTTPS endpoints only (required)
- Store your webhook secret securely (environment variables, secrets manager)
- Implement rate limiting on your webhook endpoint
- Return 200 status code quickly, process events asynchronously
6. Retry Logic & Error Handling
Tageur implements automatic retry logic for failed webhook deliveries to ensure reliable event delivery.
Retry Behavior:
- Timeout: Configurable from 1-60 seconds (default: 30s)
- Max Retries: Configurable from 0-10 attempts (default: 3)
- Retry Schedule: Exponential backoff (1m, 5m, 15m, 1h, 3h...)
- Success Criteria: HTTP 200-299 response status
When Webhooks Fail:
Deliveries are marked as failed if:
- Connection timeout or network error
- HTTP status code 4xx or 5xx
- Maximum retry attempts exceeded
Monitoring Deliveries:
View webhook delivery logs in your company settings to monitor:
- Delivery status (success, failed, pending)
- Response codes and error messages
- Retry attempts and timestamps
- Success rate metrics
7. Implementation Examples
Sync Assets to Microsoft Dynamics
// Sync new Tageur assets to Dynamics 365
app.post('/webhooks/tageur-to-dynamics', async (req, res) => {
if (!verifyWebhookSignature(req, WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const { event, data } = req.body;
if (event === 'asset.created' || event === 'asset.updated') {
try {
// Map Tageur asset to Dynamics entity
const dynamicsEntity = {
name: data.attributes.name,
serialnumber: data.attributes.serial_number,
assettype: data.attributes.asset_type,
statuscode: mapStatusToDynamics(data.attributes.status),
purchasedate: data.attributes.purchase_date,
// Custom fields
tageur_asset_id: data.id
};
// Update or create in Dynamics
await dynamicsClient.upsert('equipment', dynamicsEntity);
res.status(200).json({ synced: true });
} catch (error) {
console.error('Dynamics sync error:', error);
res.status(500).json({ error: error.message });
}
} else {
res.status(200).json({ skipped: true });
}
});
Send Notifications to Slack
# Send task completion notifications to Slack
@app.route('/webhooks/tageur-to-slack', methods=['POST'])
def notify_slack():
if not verify_webhook_signature(request, WEBHOOK_SECRET):
return jsonify({'error': 'Invalid signature'}), 401
event_data = request.json
event_type = event_data.get('event')
if event_type == 'task.completed':
data = event_data.get('data', {}).get('attributes', {})
message = {
"text": f"✅ Task Completed: {data.get('title')}",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": f"*Task:* {data.get('title')}\\n*Completed by:* {data.get('assigned_to_name')}\\n*Asset:* #{data.get('asset_id')}"
}
}
]
}
# Send to Slack webhook
requests.post(SLACK_WEBHOOK_URL, json=message)
return jsonify({'received': True}), 200
Update Inventory in Custom Database
# Rails controller to sync parts inventory
class WebhooksController < ApplicationController
skip_before_action :verify_authenticity_token
def tageur_parts
unless verify_signature(request.body.read, request.headers['X-Tageur-Signature'])
render json: { error: 'Invalid signature' }, status: :unauthorized
return
end
event_data = JSON.parse(request.body.read)
case event_data['event']
when 'part.created', 'part.updated'
sync_part_to_inventory(event_data['data'])
when 'part.deleted'
remove_part_from_inventory(event_data['data']['id'])
end
render json: { received: true }, status: :ok
end
private
def sync_part_to_inventory(part_data)
attributes = part_data['attributes']
InventoryItem.find_or_create_by(tageur_id: part_data['id']).update(
part_number: attributes['part_number'],
name: attributes['name'],
unit_cost: attributes['unit_cost'],
vendor: attributes['vendor']
)
end
end
8. Testing Webhooks
Local Development
Use tools like ngrok or localtunnel to expose your local development server for webhook testing:
# Using ngrok
ngrok http 3000
# Your webhook URL becomes:
# https://abc123.ngrok.io/webhooks/tageur
Test Webhook Delivery
Use the "Test Webhook" button in your webhook settings to send a test event and verify your endpoint is configured correctly.
Testing Checklist:
- ✓ Endpoint returns 200 OK for valid signatures
- ✓ Endpoint rejects requests with invalid signatures (401)
- ✓ Endpoint processes events asynchronously
- ✓ Response time is under configured timeout
- ✓ Idempotent handling (same event can be processed multiple times safely)
9. Bidirectional Webhooks (Receiving from External Systems)
In addition to sending webhooks to your systems, Tageur can also receive webhooks from your external ERP, CMMS, or inventory systems. This enables true bidirectional synchronization where changes in either system are automatically reflected in the other.
Use Cases:
- Push asset updates from SAP or Dynamics when equipment is purchased or relocated
- Sync parts inventory from your procurement system when stock levels change
- Create maintenance tasks in Tageur when work orders are generated in your CMMS
- Mark tasks as completed when work is finished in your external system
Endpoint and Authentication
POST https://tageur.com/api/webhooks/receive
Headers:
Authorization: Bearer YOUR_API_KEY
X-Tageur-Company-ID: YOUR_COMPANY_ID
Content-Type: application/json
Why Company ID is Required:
The X-Tageur-Company-ID header contains your company's integer ID, which enables efficient database filtering.
This allows Tageur to quickly authenticate your API key and process your webhook without expensive UUID lookups. You can find your company ID
in your account settings under "API Integration."
Supported Events
asset.created / asset.updated
Create or update asset by serial_number (upsert operation)
asset.deleted
Mark asset as retired (soft delete)
part.created / part.updated
Create or update part by part_number (upsert operation)
part.deleted
Mark part as discontinued
task.created / task.updated
Create or update maintenance task
task.completed
Mark task as completed with timestamp
Example: Push Asset from ERP to Tageur
// Microsoft Dynamics 365 webhook integration
const axios = require('axios');
// Dynamics webhook handler
app.post('/webhooks/dynamics-to-tageur', async (req, res) => {
const dynamicsAsset = req.body;
// Map Dynamics fields to Tageur format
const tageurPayload = {
event: 'asset.created',
timestamp: new Date().toISOString(),
data: {
type: 'asset',
attributes: {
name: dynamicsAsset.Name,
serial_number: dynamicsAsset.SerialNumber,
asset_type: dynamicsAsset.AssetCategory,
status: dynamicsAsset.Status === 'Active' ? 'operational' : 'retired',
brand: dynamicsAsset.Manufacturer,
model_number: dynamicsAsset.ModelNumber,
location: dynamicsAsset.Location,
purchase_date: dynamicsAsset.PurchaseDate,
extra_values: {
dynamics_id: dynamicsAsset.AssetId,
cost_center: dynamicsAsset.CostCenter,
department: dynamicsAsset.Department,
purchase_cost: dynamicsAsset.PurchaseCost // Store in extra_values instead
}
}
}
};
try {
// Send to Tageur
const response = await axios.post(
'https://tageur.com/api/webhooks/receive',
tageurPayload,
{
headers: {
'Authorization': `Bearer ${process.env.TAGEUR_API_KEY}`,
'X-Tageur-Company-ID': process.env.TAGEUR_COMPANY_ID,
'Content-Type': 'application/json'
}
}
);
console.log('Asset synced to Tageur:', response.data);
res.status(200).json({ success: true });
} catch (error) {
console.error('Failed to sync to Tageur:', error.response?.data);
res.status(500).json({ error: 'Sync failed' });
}
});
Example: Complete Task from CMMS
# When work order is completed in your CMMS, mark task complete in Tageur
import requests
import os
def complete_tageur_task(work_order):
"""Complete Tageur task when CMMS work order is finished"""
payload = {
"event": "task.completed",
"timestamp": work_order.completion_time.isoformat(),
"data": {
"type": "task",
"attributes": {
"external_id": work_order.id, # Match by external ID
"task_id": work_order.tageur_task_id # Or by Tageur task ID
}
}
}
response = requests.post(
'https://tageur.com/api/webhooks/receive',
json=payload,
headers={
'Authorization': f'Bearer {os.environ["TAGEUR_API_KEY"]}',
'X-Tageur-Company-ID': os.environ['TAGEUR_COMPANY_ID'],
'Content-Type': 'application/json'
}
)
if response.status_code == 200:
print(f"Task completed in Tageur: {response.json()}")
else:
print(f"Error: {response.json()}")