Webhooks allow external systems to receive real-time notifications when events occur in TGM Expert. When an event is triggered (e.g., an inspection is created), TGM Expert sends an HTTP POST request to your configured URL with details about the event.

Table of Contents


Overview

Key Features

  • 70+ Event Types - Cover all major entities (inspections, units, alerts, etc.)
  • Wildcard Subscription - Subscribe to all events with *
  • HMAC-SHA256 Signatures - Cryptographic payload verification
  • Automatic Retries - Exponential backoff on failures
  • Delivery Tracking - Full history of all deliveries
  • Auto-Disable - Automatically disable after consecutive failures
  • Multi-Tenant Scope - Webhooks are per-company within each client

Multi-Tenancy Context

Webhooks operate within TGM's multi-tenant architecture:

┌─────────────────────────────────────────────────────────────────┐
│                       Platform Level                             │
│  Webhook processing engine (shared)                             │
└─────────────────────────────────────────────────────────────────┘
                               │
        ┌──────────────────────┼──────────────────────┐
        ▼                      ▼                      ▼
┌───────────────┐      ┌───────────────┐      ┌───────────────┐
│   Client A    │      │   Client B    │      │   Client C    │
│   Database    │      │   Database    │      │   Database    │
├───────────────┤      ├───────────────┤      ├───────────────┤
│   webhooks    │      │   webhooks    │      │   webhooks    │
│   (table)     │      │   (table)     │      │   (table)     │
│               │      │               │      │               │
│  Webhook A-1  │      │  Webhook B-1  │      │  Webhook C-1  │
│  (company 1)  │      │  (company 1)  │      │  (company 1)  │
└───────────────┘      └───────────────┘      └───────────────┘

Key Points

  1. Webhooks are per-company - Each company can have its own webhook configurations
  2. Events trigger within client context - Only events from that client's database trigger webhooks
  3. Webhook configs stored per-client - The webhooks table is in each client's database

Webhook Payload Includes Client Info

{
  "event": "inspection.created",
  "timestamp": "2026-02-05T10:30:00Z",
  "client": {
    "id": "acme-corp",
    "name": "Acme Corporation"
  },
  "company": {
    "id": 1,
    "name": "Acme HQ"
  },
  "data": { ... }
}

How It Works

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│  TGM Expert │────>│   Webhook   │────>│ Your Server │
│   (Event)   │     │   Service   │     │  (Endpoint) │
└─────────────┘     └─────────────┘     └─────────────┘
       │                   │                   │
       │ 1. Event occurs   │                   │
       │──────────────────>│                   │
       │                   │ 2. POST payload   │
       │                   │──────────────────>│
       │                   │                   │
       │                   │ 3. 2xx response   │
       │                   │<──────────────────│
       │                   │                   │

Configuration

Creating a Webhook

curl -X POST https://api.tgm-expert.com/api/admin/webhooks \
  -H "Authorization: Bearer $JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My Integration Webhook",
    "description": "Sends events to our internal system",
    "url": "https://your-server.com/webhooks/tgm",
    "companyId": 1,
    "events": ["inspection.created", "inspection.updated", "alert.created"],
    "httpMethod": "POST",
    "contentType": "application/json",
    "timeoutMs": 30000,
    "maxRetries": 3,
    "verifySsl": true,
    "autoDisableAfterFailures": 10
  }'

Response

{
  "data": {
    "id": 1,
    "name": "My Integration Webhook",
    "url": "https://your-server.com/webhooks/tgm",
    "isActive": true,
    "events": ["inspection.created", "inspection.updated", "alert.created"],
    "hasSecret": true,
    "secretPreview": "whsec_abc12345...xyz9",
    "createdAt": "2026-02-01T00:00:00Z"
  },
  "meta": {
    "message": "Webhook created. Save the secret key - it won't be shown again."
  }
}

Important: Save the secretPreview value immediately. The full secret is only shown once during creation.

Configuration Options

Field Type Required Default Description
name string Yes - Webhook name (max 100 chars)
description string No - Description (max 500 chars)
url string Yes - Endpoint URL (https recommended)
companyId number No null Company ID (null for system-wide)
events string[] No [] Event codes to subscribe to
customHeaders object No {} Custom HTTP headers
httpMethod string No POST HTTP method (POST, PUT, PATCH)
contentType string No application/json Content-Type header
timeoutMs number No 30000 Request timeout in milliseconds
maxRetries number No 3 Maximum retry attempts
verifySsl boolean No true Verify SSL certificates
autoDisableAfterFailures number No 10 Auto-disable threshold (0 = never)

Event Types

Event Naming Convention

Events follow the pattern: {resource}.{action}

Examples: - inspection.created - Inspection was created - unit.updated - Unit was updated - alert.resolved - Alert was resolved

Available Events

Asset Events

Event Description
location.created Location/plant created
location.updated Location/plant updated
location.deleted Location/plant deleted
unit.created Unit created
unit.updated Unit updated
unit.deleted Unit deleted
component.created Component created
component.updated Component updated
component.deleted Component deleted

Maintenance Events

Event Description
inspection.created Inspection created
inspection.updated Inspection updated
inspection.deleted Inspection deleted
intervention.created Intervention created
intervention.updated Intervention updated
intervention.deleted Intervention deleted
work_order.created Work order created
work_order.updated Work order updated
work_order.deleted Work order deleted
work_order.status_changed Work order status changed
failure.created Failure recorded
failure.updated Failure updated
failure.deleted Failure deleted

Monitoring Events

Event Description
alert.created Alert triggered
alert.updated Alert updated
alert.acknowledged Alert acknowledged
alert.resolved Alert resolved
sensor.created Sensor configured
sensor.updated Sensor updated
sensor.deleted Sensor removed
sensor.reading_received Sensor reading received

Lifecycle Events

Event Description
asset_lifecycle.created Lifecycle record created
asset_lifecycle.updated Lifecycle updated
asset_lifecycle.deleted Lifecycle deleted
maintenance_plan.created Maintenance plan created
maintenance_plan.updated Maintenance plan updated
maintenance_plan.due Maintenance is due
spare_part.low_stock Spare part stock is low

Document Events

Event Description
document.created Document uploaded
document.updated Document updated
document.deleted Document deleted
drawing.created Drawing uploaded
drawing.updated Drawing updated
drawing.deleted Drawing deleted

Communication Events

Event Description
notification.created Notification created
comment.created Comment added
comment.updated Comment updated
comment.deleted Comment deleted
todo.created Todo created
todo.updated Todo updated
todo.completed Todo completed

Wildcard

Event Description
* Subscribe to ALL events

Payload Structure

Standard Payload

{
  "eventId": "550e8400-e29b-41d4-a716-446655440000",
  "event": "inspection.created",
  "timestamp": "2026-02-01T12:30:45.123Z",
  "webhookId": 1,
  "apiVersion": "1.0",
  "data": {
    "id": 12345,
    "type": "Inspection",
    "attributes": {
      "id": 12345,
      "title": "Monthly Turbine Inspection",
      "status": "pending",
      "scheduledDate": "2026-02-15",
      "unit": {
        "id": 100,
        "name": "Turbine Unit 1"
      },
      "inspector": {
        "id": 50,
        "name": "John Smith"
      },
      "createdAt": "2026-02-01T12:30:45Z",
      "updatedAt": "2026-02-01T12:30:45Z"
    },
    "previous": null
  },
  "metadata": null
}

Update Payload (with previous state)

{
  "eventId": "550e8400-e29b-41d4-a716-446655440001",
  "event": "inspection.updated",
  "timestamp": "2026-02-01T14:00:00.000Z",
  "webhookId": 1,
  "apiVersion": "1.0",
  "data": {
    "id": 12345,
    "type": "Inspection",
    "attributes": {
      "id": 12345,
      "title": "Monthly Turbine Inspection",
      "status": "completed",
      "completedAt": "2026-02-01T14:00:00Z"
    },
    "previous": {
      "status": "in_progress"
    }
  }
}

Delete Payload

{
  "eventId": "550e8400-e29b-41d4-a716-446655440002",
  "event": "inspection.deleted",
  "timestamp": "2026-02-01T15:00:00.000Z",
  "webhookId": 1,
  "apiVersion": "1.0",
  "data": {
    "id": 12345,
    "type": "Inspection",
    "attributes": {
      "id": 12345,
      "title": "Monthly Turbine Inspection"
    }
  }
}

Security

Signature Verification

Every webhook request includes an HMAC-SHA256 signature in the X-TGM-Signature header. Use this to verify the payload authenticity.

Signature Header Format

X-TGM-Signature: sha256=5d41402abc4b2a76b9719d911017c592

Verification Example (Node.js)

const crypto = require('crypto');

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

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

// Express middleware
app.post('/webhooks/tgm', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-tgm-signature'];
  const payload = req.body.toString();

  if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(payload);
  // Process event...

  res.status(200).send('OK');
});

Verification Example (Python)

import hmac
import hashlib

def verify_signature(payload: str, signature: str, secret: str) -> bool:
    expected = 'sha256=' + hmac.new(
        secret.encode('utf-8'),
        payload.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

# Flask example
@app.route('/webhooks/tgm', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-TGM-Signature')
    payload = request.get_data(as_text=True)

    if not verify_signature(payload, signature, os.environ['WEBHOOK_SECRET']):
        return 'Invalid signature', 401

    event = request.get_json()
    # Process event...

    return 'OK', 200

Request Headers

Header Description
X-TGM-Signature HMAC-SHA256 signature
X-TGM-Event Event type (e.g., inspection.created)
X-TGM-Delivery Unique delivery ID
X-TGM-Timestamp Unix timestamp of request
Content-Type application/json

Delivery & Retries

Retry Schedule

Failed deliveries are retried with exponential backoff:

Attempt Delay
1 Immediate
2 1 minute
3 5 minutes
4 15 minutes

After all retries are exhausted, the delivery is marked as failed.

Success Criteria

A delivery is considered successful when: - HTTP status code is 2xx (200-299) - Response is received within the timeout period

Auto-Disable

If a webhook fails autoDisableAfterFailures consecutive times (default: 10), it is automatically disabled. The webhook can be re-enabled via the admin API, which resets the failure counter.

Viewing Delivery History

curl -X GET "https://api.tgm-expert.com/api/admin/webhooks/1/deliveries?limit=10" \
  -H "Authorization: Bearer $JWT_TOKEN"

Response:

{
  "data": [
    {
      "id": 100,
      "webhookId": 1,
      "eventType": "inspection.created",
      "status": "SUCCESS",
      "attemptCount": 1,
      "responseStatus": 200,
      "durationMs": 150,
      "createdAt": "2026-02-01T12:30:45Z"
    },
    {
      "id": 99,
      "webhookId": 1,
      "eventType": "alert.created",
      "status": "FAILED",
      "attemptCount": 3,
      "responseStatus": 500,
      "errorMessage": "Internal Server Error",
      "durationMs": 30000,
      "createdAt": "2026-02-01T12:00:00Z"
    }
  ]
}


Admin API

Endpoints

Method Endpoint Description
GET /api/admin/webhooks/event-types List all event types
GET /api/admin/webhooks/company/{id} List company webhooks
GET /api/admin/webhooks/{id} Get webhook details
POST /api/admin/webhooks Create webhook
PUT /api/admin/webhooks/{id} Update webhook
DELETE /api/admin/webhooks/{id} Delete webhook
POST /api/admin/webhooks/{id}/enable Enable webhook
POST /api/admin/webhooks/{id}/disable Disable webhook
POST /api/admin/webhooks/{id}/regenerate-secret Regenerate secret
POST /api/admin/webhooks/{id}/test Send test ping
GET /api/admin/webhooks/{id}/deliveries Get delivery history
GET /api/admin/webhooks/deliveries/{id} Get delivery details
POST /api/admin/webhooks/deliveries/{id}/retry Retry failed delivery

Testing a Webhook

Send a test ping to verify your endpoint is working:

curl -X POST "https://api.tgm-expert.com/api/admin/webhooks/1/test" \
  -H "Authorization: Bearer $JWT_TOKEN"

The test payload:

{
  "eventId": "ping-1706745600000",
  "event": "webhook.ping",
  "timestamp": "2026-02-01T12:00:00Z",
  "webhookId": 1,
  "apiVersion": "1.0",
  "data": {
    "type": "WebhookTest",
    "attributes": {
      "message": "This is a test webhook delivery"
    }
  }
}


Best Practices

1. Always Verify Signatures

Never trust incoming webhooks without verifying the HMAC signature.

2. Respond Quickly

Return a 2xx response as quickly as possible. Process the webhook asynchronously if needed.

app.post('/webhooks/tgm', (req, res) => {
  // Acknowledge immediately
  res.status(200).send('OK');

  // Process asynchronously
  processWebhookAsync(req.body);
});

3. Handle Duplicates

The same event may be delivered multiple times. Use eventId for deduplication.

const processedEvents = new Set();

function handleWebhook(event) {
  if (processedEvents.has(event.eventId)) {
    return; // Already processed
  }
  processedEvents.add(event.eventId);
  // Process event...
}

4. Use HTTPS

Always use HTTPS endpoints in production to ensure payload encryption in transit.

5. Monitor Failures

Regularly check your webhook's health via the admin API. Set up alerts for high failure rates.

6. Subscribe Only to Needed Events

Don't use * wildcard unless you truly need all events. Subscribe only to events you'll actually process.

7. Handle Payload Size

Payloads are limited to 1MB. For large entities, only essential fields are included.


Troubleshooting

Webhook Not Receiving Events

  1. Check if webhook is active: GET /api/admin/webhooks/{id}
  2. Check if subscribed to correct events: Verify events array
  3. Check delivery history: GET /api/admin/webhooks/{id}/deliveries
  4. Send test ping: POST /api/admin/webhooks/{id}/test

Signature Verification Failing

  1. Use the raw request body (before JSON parsing)
  2. Ensure you're using the correct secret
  3. Check for encoding issues (UTF-8)
  4. Use constant-time comparison to prevent timing attacks

High Failure Rate

  1. Check your endpoint's response time (should be < 30s)
  2. Ensure endpoint returns 2xx status
  3. Check server logs for errors
  4. Increase timeoutMs if processing takes time

Rate Limits

Webhook deliveries are subject to rate limiting:

Limit Value
Max webhooks per company 20
Max events per second 100
Max payload size 1 MB
Max retries 3
Delivery retention 30 days