---
title: Webhooks API
description: Configure and manage webhooks for real-time event notifications from TraceRTM
---

# Webhooks API

Webhooks enable real-time notifications when events occur in TraceRTM. Configure endpoints to receive HTTP callbacks for item changes, link updates, workflow transitions, and more.

## Endpoints Overview

| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/projects/{project_id}/webhooks` | List webhooks |
| `POST` | `/projects/{project_id}/webhooks` | Create webhook |
| `GET` | `/projects/{project_id}/webhooks/{id}` | Get webhook |
| `PUT` | `/projects/{project_id}/webhooks/{id}` | Update webhook |
| `DELETE` | `/projects/{project_id}/webhooks/{id}` | Delete webhook |
| `POST` | `/projects/{project_id}/webhooks/{id}/test` | Test webhook |
| `GET` | `/projects/{project_id}/webhooks/{id}/deliveries` | List deliveries |
| `POST` | `/projects/{project_id}/webhooks/{id}/deliveries/{delivery_id}/redeliver` | Redeliver |

---

## Create Webhook

### `POST /projects/{project_id}/webhooks`

Create a new webhook subscription.

#### Request Body

```json
{
  "name": "CI/CD Pipeline Trigger",
  "description": "Trigger pipeline when requirements are approved",
  "url": "https://ci.example.com/webhooks/tracertm",
  "events": [
    "item.created",
    "item.updated",
    "item.status_changed",
    "link.created"
  ],
  "filters": {
    "item_types": ["requirement", "test_case"],
    "statuses": ["approved"],
    "tags": ["automated"]
  },
  "config": {
    "content_type": "application/json",
    "secret": "whsec_your_webhook_secret",
    "ssl_verification": true,
    "timeout_seconds": 30
  },
  "retry_config": {
    "max_attempts": 5,
    "backoff_type": "exponential",
    "initial_delay_seconds": 5
  },
  "headers": {
    "X-Custom-Header": "custom-value",
    "Authorization": "Bearer ${WEBHOOK_AUTH_TOKEN}"
  },
  "active": true
}
```

#### Webhook Configuration

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | Yes | Webhook name (max 100 chars) |
| `url` | string | Yes | HTTPS endpoint URL |
| `events` | array | Yes | Events to subscribe to |
| `filters` | object | No | Filter conditions |
| `config.content_type` | string | No | `application/json` or `application/x-www-form-urlencoded` |
| `config.secret` | string | Yes | Signing secret for verification |
| `config.ssl_verification` | boolean | No | Verify SSL certificates (default: true) |
| `config.timeout_seconds` | integer | No | Request timeout (default: 30, max: 60) |
| `headers` | object | No | Custom headers to include |
| `active` | boolean | No | Enable/disable webhook (default: true) |

#### Response

```json
{
  "id": "wh_abc123",
  "name": "CI/CD Pipeline Trigger",
  "description": "Trigger pipeline when requirements are approved",
  "url": "https://ci.example.com/webhooks/tracertm",
  "events": [
    "item.created",
    "item.updated",
    "item.status_changed",
    "link.created"
  ],
  "filters": {
    "item_types": ["requirement", "test_case"],
    "statuses": ["approved"],
    "tags": ["automated"]
  },
  "config": {
    "content_type": "application/json",
    "ssl_verification": true,
    "timeout_seconds": 30
  },
  "retry_config": {
    "max_attempts": 5,
    "backoff_type": "exponential"
  },
  "active": true,
  "stats": {
    "total_deliveries": 0,
    "successful_deliveries": 0,
    "failed_deliveries": 0,
    "last_delivery_at": null
  },
  "created_at": "2024-01-20T10:00:00Z",
  "updated_at": "2024-01-20T10:00:00Z"
}
```

---

## Webhook Events

### Available Events

| Event | Description |
|-------|-------------|
| `item.created` | Item created |
| `item.updated` | Item updated (any field) |
| `item.deleted` | Item deleted |
| `item.status_changed` | Item status changed |
| `item.assigned` | Item assigned/unassigned |
| `item.commented` | Comment added to item |
| `link.created` | Link created |
| `link.deleted` | Link deleted |
| `project.updated` | Project settings updated |
| `workflow.completed` | Workflow completed |
| `import.completed` | Import job completed |
| `export.completed` | Export job completed |
| `batch.completed` | Batch operation completed |

### Wildcard Events

```json
{
  "events": ["item.*"]        // All item events
}
```

```json
{
  "events": ["*"]             // All events (use with caution)
}
```

---

## Webhook Payload

### Payload Structure

```json
{
  "id": "evt_delivery_abc123",
  "webhook_id": "wh_abc123",
  "event": "item.status_changed",
  "timestamp": "2024-01-20T10:30:00Z",
  "project": {
    "id": "proj_abc123",
    "name": "My Project"
  },
  "data": {
    "item": {
      "id": "item_req_001",
      "type": "requirement",
      "title": "User Authentication",
      "status": "approved",
      "previous_status": "in_review"
    },
    "changes": {
      "status": {
        "from": "in_review",
        "to": "approved"
      }
    },
    "actor": {
      "id": "user_abc123",
      "email": "user@example.com",
      "name": "Alice Smith"
    }
  },
  "metadata": {
    "delivery_attempt": 1,
    "correlation_id": "corr_xyz789"
  }
}
```

### Event-Specific Payloads

#### item.created

```json
{
  "event": "item.created",
  "data": {
    "item": {
      "id": "item_req_001",
      "type": "requirement",
      "title": "New Requirement",
      "description": "Description here...",
      "status": "draft",
      "priority": "high",
      "assignee": null,
      "tags": ["new"],
      "created_at": "2024-01-20T10:00:00Z"
    },
    "actor": {
      "id": "user_abc123",
      "email": "user@example.com"
    }
  }
}
```

#### link.created

```json
{
  "event": "link.created",
  "data": {
    "link": {
      "id": "link_abc123",
      "type": "verified_by",
      "source": {
        "id": "item_req_001",
        "type": "requirement",
        "title": "Authentication Requirement"
      },
      "target": {
        "id": "item_tc_001",
        "type": "test_case",
        "title": "TC-001: Login Test"
      }
    },
    "actor": {
      "id": "user_abc123",
      "email": "user@example.com"
    }
  }
}
```

---

## Webhook Security

### Signature Verification

TraceRTM signs all webhook payloads using HMAC-SHA256. Verify signatures to ensure authenticity.

#### Headers

```
X-TraceRTM-Signature: sha256=5d5b09f6dcb2d53a5fffc60c4ac0d55fabdf556068d72f2dfa45e92cd8a8b3f2
X-TraceRTM-Timestamp: 1705747800
X-TraceRTM-Delivery-ID: del_abc123
```

#### Verification Example (Python)

```python
import hmac
import hashlib
import time

def verify_webhook_signature(payload: bytes, signature: str, secret: str, timestamp: str) -> bool:
    # Check timestamp is within 5 minutes
    current_time = int(time.time())
    webhook_time = int(timestamp)
    if abs(current_time - webhook_time) > 300:
        return False  # Replay attack protection

    # Compute expected signature
    signed_payload = f"{timestamp}.{payload.decode('utf-8')}"
    expected_signature = hmac.new(
        secret.encode('utf-8'),
        signed_payload.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()

    # Compare signatures
    return hmac.compare_digest(f"sha256={expected_signature}", signature)

# Usage in Flask
from flask import Flask, request

app = Flask(__name__)
WEBHOOK_SECRET = "whsec_your_secret"

@app.route('/webhook', methods=['POST'])
def webhook_handler():
    signature = request.headers.get('X-TraceRTM-Signature')
    timestamp = request.headers.get('X-TraceRTM-Timestamp')

    if not verify_webhook_signature(request.data, signature, WEBHOOK_SECRET, timestamp):
        return {'error': 'Invalid signature'}, 401

    event = request.json
    print(f"Received event: {event['event']}")

    return {'received': True}, 200
```

#### Verification Example (Node.js)

```javascript
const crypto = require('crypto');
const express = require('express');

const app = express();
const WEBHOOK_SECRET = 'whsec_your_secret';

function verifyWebhookSignature(payload, signature, timestamp) {
  // Check timestamp
  const currentTime = Math.floor(Date.now() / 1000);
  if (Math.abs(currentTime - parseInt(timestamp)) > 300) {
    return false;
  }

  // Compute signature
  const signedPayload = `${timestamp}.${payload}`;
  const expectedSignature = crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(signedPayload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(`sha256=${expectedSignature}`),
    Buffer.from(signature)
  );
}

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-tracertm-signature'];
  const timestamp = req.headers['x-tracertm-timestamp'];

  if (!verifyWebhookSignature(req.body.toString(), signature, timestamp)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const event = JSON.parse(req.body);
  console.log(`Received event: ${event.event}`);

  res.json({ received: true });
});
```

---

## Test Webhook

### `POST /projects/{project_id}/webhooks/{id}/test`

Send a test payload to verify webhook configuration.

#### Request Body

```json
{
  "event": "item.created"
}
```

#### Response

```json
{
  "success": true,
  "delivery_id": "del_test_abc123",
  "response": {
    "status_code": 200,
    "body": "{\"received\": true}",
    "headers": {
      "content-type": "application/json"
    },
    "duration_ms": 145
  }
}
```

---

## List Deliveries

### `GET /projects/{project_id}/webhooks/{id}/deliveries`

Get delivery history for a webhook.

#### Query Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `status` | string | Filter by status: `success`, `failed`, `pending` |
| `event` | string | Filter by event type |
| `start_date` | string | Filter by date range start |
| `end_date` | string | Filter by date range end |
| `limit` | integer | Results per page (default: 20) |

#### Response

```json
{
  "deliveries": [
    {
      "id": "del_abc123",
      "event": "item.status_changed",
      "status": "success",
      "request": {
        "url": "https://ci.example.com/webhooks/tracertm",
        "method": "POST",
        "headers": {
          "Content-Type": "application/json",
          "X-TraceRTM-Signature": "sha256=..."
        }
      },
      "response": {
        "status_code": 200,
        "body": "{\"received\": true}",
        "duration_ms": 145
      },
      "attempts": 1,
      "created_at": "2024-01-20T10:30:00Z",
      "delivered_at": "2024-01-20T10:30:00Z"
    },
    {
      "id": "del_def456",
      "event": "item.created",
      "status": "failed",
      "request": {
        "url": "https://ci.example.com/webhooks/tracertm",
        "method": "POST"
      },
      "response": {
        "status_code": 500,
        "body": "Internal Server Error",
        "duration_ms": 5023
      },
      "attempts": 5,
      "error": "Max retry attempts exceeded",
      "created_at": "2024-01-20T09:00:00Z",
      "last_attempt_at": "2024-01-20T09:15:00Z"
    }
  ],
  "total": 156,
  "stats": {
    "success_rate": 0.98,
    "avg_response_time_ms": 178
  }
}
```

---

## Retry Configuration

### Retry Strategies

| Strategy | Description | Delays |
|----------|-------------|--------|
| `exponential` | Exponential backoff | 5s, 10s, 20s, 40s, 80s |
| `linear` | Linear backoff | 5s, 10s, 15s, 20s, 25s |
| `fixed` | Fixed intervals | 30s, 30s, 30s, 30s, 30s |

### Retry Conditions

Webhooks are retried for:
- HTTP 5xx errors
- Network timeouts
- Connection failures

Webhooks are **not** retried for:
- HTTP 2xx (success)
- HTTP 4xx (client errors)
- Invalid signatures from the receiving server

---

## SDK Examples

### Python

```python
from tracertm import TraceRTMClient

client = TraceRTMClient(api_key="rtm_live_sk_...")

# Create webhook
webhook = client.webhooks.create(
    project_id="proj_abc123",
    name="CI/CD Trigger",
    url="https://ci.example.com/webhooks/tracertm",
    events=["item.status_changed", "item.created"],
    filters={
        "item_types": ["requirement"],
        "statuses": ["approved"]
    },
    config={
        "secret": "whsec_your_secret",
        "timeout_seconds": 30
    }
)

print(f"Created webhook: {webhook.id}")

# List webhooks
webhooks = client.webhooks.list(project_id="proj_abc123")
for wh in webhooks:
    print(f"{wh.name}: {wh.stats.successful_deliveries}/{wh.stats.total_deliveries} successful")

# Test webhook
test_result = client.webhooks.test(
    project_id="proj_abc123",
    webhook_id="wh_abc123",
    event="item.created"
)

if test_result.success:
    print(f"Test successful: {test_result.response.status_code}")
else:
    print(f"Test failed: {test_result.error}")

# Get delivery history
deliveries = client.webhooks.list_deliveries(
    project_id="proj_abc123",
    webhook_id="wh_abc123",
    status="failed",
    limit=10
)

for delivery in deliveries:
    print(f"{delivery.id}: {delivery.event} - {delivery.error}")

# Redeliver failed webhook
client.webhooks.redeliver(
    project_id="proj_abc123",
    webhook_id="wh_abc123",
    delivery_id="del_def456"
)

# Disable webhook
client.webhooks.update(
    project_id="proj_abc123",
    webhook_id="wh_abc123",
    active=False
)
```

### TypeScript

```typescript
import { TraceRTMClient } from '@tracertm/sdk';

const client = new TraceRTMClient({ apiKey: 'rtm_live_sk_...' });

// Create webhook
const webhook = await client.webhooks.create({
  projectId: 'proj_abc123',
  name: 'CI/CD Trigger',
  url: 'https://ci.example.com/webhooks/tracertm',
  events: ['item.status_changed', 'item.created'],
  filters: {
    itemTypes: ['requirement'],
    statuses: ['approved']
  },
  config: {
    secret: 'whsec_your_secret',
    timeoutSeconds: 30
  }
});

// Test webhook
const testResult = await client.webhooks.test({
  projectId: 'proj_abc123',
  webhookId: webhook.id,
  event: 'item.created'
});

console.log(`Test result: ${testResult.success ? 'Success' : 'Failed'}`);

// Handle webhook in Express
import express from 'express';
import { verifyWebhookSignature } from '@tracertm/sdk';

const app = express();

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const isValid = verifyWebhookSignature({
    payload: req.body,
    signature: req.headers['x-tracertm-signature'] as string,
    timestamp: req.headers['x-tracertm-timestamp'] as string,
    secret: 'whsec_your_secret'
  });

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

  const event = JSON.parse(req.body);

  switch (event.event) {
    case 'item.created':
      handleItemCreated(event.data);
      break;
    case 'item.status_changed':
      handleStatusChanged(event.data);
      break;
  }

  res.json({ received: true });
});
```

---

## Best Practices

### Endpoint Design

1. **Return quickly**: Process webhooks asynchronously, return 200 immediately
2. **Be idempotent**: Handle duplicate deliveries gracefully
3. **Use queues**: Push webhook data to a queue for reliable processing
4. **Log everything**: Log delivery IDs for troubleshooting

### Security

1. **Always verify signatures**: Never process unverified webhooks
2. **Use HTTPS**: Only use secure endpoints
3. **Rotate secrets**: Periodically rotate webhook secrets
4. **IP allowlisting**: Optionally allowlist TraceRTM IPs

### Reliability

1. **Handle retries**: Expect and handle retry deliveries
2. **Monitor failures**: Alert on webhook delivery failures
3. **Test regularly**: Use test endpoints to verify configuration

---

## Related Documentation

- [Webhook Events](/docs/api-reference/rest-api/webhook-events) - Detailed event reference
- [Rate Limiting](/docs/api-reference/rest-api/rate-limiting) - Rate limits for webhooks
- [Authentication](/docs/api-reference/authentication) - API authentication
