When a team member moves a Trello card to "In Progress" or adds a critical comment, you need to know immediately—not when your polling script checks in 5 minutes. Trello webhooks solve this problem by sending real-time HTTP POST notifications to your server the moment board actions occur, enabling you to:
- Sync project data instantly between Trello and your custom dashboard or CRM
- Trigger automation workflows when cards move between lists or checklists are completed
- Track team activity with detailed logs of who changed what and when
- Send notifications via Slack, email, or SMS when important cards are updated
- Build custom integrations that extend Trello's functionality for your specific workflow
Trello's webhook system uses a unique HMAC-SHA1 signature approach—instead of signing just the request body, Trello concatenates the body + your callback URL before hashing. This provides an extra layer of security that prevents signature replay attacks even if attackers intercept payloads. In this comprehensive guide, you'll learn how to create Trello webhooks programmatically, implement the unique signature verification in Node.js, Python, and PHP, handle all action types, and build production-ready webhook endpoints.
Testing your Trello integration is crucial before going live. Our Webhook Payload Generator tool lets you create properly signed Trello webhook payloads with custom event data, allowing you to test your signature verification logic (including the unique body + callbackURL concatenation) without creating actual webhooks or exposing your local development environment.
What Are Trello Webhooks?
Trello webhooks are HTTP POST callbacks that Atlassian Trello sends to your specified endpoint URL whenever actions occur on a Trello board, list, card, or member you're monitoring. Unlike traditional API polling where your application repeatedly queries Trello's servers asking "did anything happen?", webhooks invert this model—Trello proactively notifies your server the instant a card is created, moved, commented on, or any other action occurs.
The webhook architecture follows this flow:
[Board Action Occurs] → [Trello Server] → [HTTP POST to Your Endpoint] → [Your Application Logic]
Trello webhooks differ from generic webhooks and other provider implementations in several important ways:
- No Dashboard Configuration: Webhooks are created entirely via REST API calls, not through the Trello web interface
- Model-Based Subscriptions: Subscribe to an entire board, list, card, or member—you cannot filter to specific event types
- Unique Signature Method: Concatenates request body + callback URL before HMAC-SHA1 signing (prevents signature replay)
- HEAD Request Verification: Trello sends a HEAD request to your endpoint before creating the webhook to verify it's accessible
- No Automatic Retry: Failed deliveries don't trigger retries—webhooks become inactive after repeated failures
Benefits specific to Trello webhooks:
- Real-time tracking of 40+ action types (card movements, comments, attachments, checklist changes, etc.)
- Subscribe to entire boards or individual cards for granular control
- Signature includes callback URL, preventing cross-endpoint signature replay attacks
- Simple JSON payload structure with consistent action/model format
- Built-in support for monitoring board changes, member activity, and card updates
Prerequisites for setting up Trello webhooks:
- Active Trello account with boards you want to monitor
- Trello API key and app secret from https://trello.com/app-key
- User token with read permissions for the boards you'll monitor
- Publicly accessible HTTPS endpoint (localhost won't work without tunneling)
- SSL/TLS certificate on your webhook endpoint (required for security)
- Ability to handle HTTP HEAD requests for endpoint verification
Understanding Trello's Unique Signature Approach
Before diving into setup, it's critical to understand how Trello's signature verification differs from other webhook providers.
Standard Webhook Signature (e.g., GitHub, Stripe):
signature = HMAC-SHA256(request_body, secret_key)
Trello's Unique Signature:
signature = HMAC-SHA1(request_body + callback_url, app_secret)
Why This Matters:
The callback URL is part of the signed data, which means:
- Different URLs get different signatures - Even if two webhooks receive identical payloads, their signatures will differ if registered to different callback URLs
- Signature replay prevention - Attackers cannot capture a webhook from one endpoint and replay it to another
- Callback URL validation - You must know and include your registered callback URL when verifying signatures
Example:
Payload: {"action": {"type": "createCard"}}
Callback URL A: https://app.example.com/webhooks/trello
Callback URL B: https://other.example.com/webhooks/trello
Even though the payload is identical, the signatures will be completely different because Trello concatenates:
- URL A:
{"action": {"type": "createCard"}}https://app.example.com/webhooks/trello - URL B:
{"action": {"type": "createCard"}}https://other.example.com/webhooks/trello
This unique approach means you must store your callback URL alongside your app secret to verify signatures correctly.
Setting Up Trello Webhooks
Unlike most webhook providers, Trello webhooks are created programmatically via API calls, not through a web dashboard. Follow these step-by-step instructions.
Step 1: Get Your API Credentials
-
Navigate to Trello API Key Page
- Visit https://trello.com/app-key in your browser
- Log in to your Trello account if prompted
-
Copy Your API Key
- Your API key is displayed at the top of the page
- Copy and store this securely (e.g.,
YOUR_API_KEY)
-
Copy Your App Secret
- Scroll down to find "App Secret" section
- Click "Show" to reveal your app secret
- Copy and store this securely (e.g.,
YOUR_APP_SECRET) - This secret is used for webhook signature verification
-
Generate a User Token
- Click the "Token" link on the same page
- Authorize the token with "read" permissions
- Copy the generated token (e.g.,
YOUR_TOKEN) - This token authenticates API requests to create webhooks
Store these securely in environment variables:
TRELLO_API_KEY=your_api_key_here
TRELLO_APP_SECRET=your_app_secret_here
TRELLO_TOKEN=your_user_token_here
Step 2: Identify the Model ID
Trello webhooks subscribe to a "model" - typically a board ID, but can also be a list, card, or member ID.
Get Board ID:
- Open the Trello board you want to monitor
- Add
.jsonto the board URL in your browser- Example:
https://trello.com/b/abc123/my-board→https://trello.com/b/abc123/my-board.json
- Example:
- Find the
idfield in the JSON response - Copy the board ID (e.g.,
board_jkl012)
Alternative: Use the Trello API
# List all boards for your account
curl "https://api.trello.com/1/members/me/boards?key=YOUR_API_KEY&token=YOUR_TOKEN"
# Find your board's ID in the response
Step 3: Prepare Your Webhook Endpoint
Your endpoint must:
- Respond to HEAD requests - Trello verifies endpoint accessibility before creating the webhook
- Return 200 status for HEAD requests
- Handle POST requests with JSON payloads
- Respond quickly (within 30 seconds)
Minimal endpoint example (Node.js/Express):
const express = require('express');
const app = express();
// IMPORTANT: Trello sends HEAD request to verify endpoint
app.head('/webhooks/trello', (req, res) => {
res.status(200).send();
});
// Handle webhook payloads
app.post('/webhooks/trello', express.json(), (req, res) => {
console.log('Received Trello webhook:', req.body);
res.status(200).send();
});
app.listen(3000, () => {
console.log('Webhook server listening on port 3000');
});
Deploy your endpoint or use ngrok for local testing:
# If testing locally with ngrok
ngrok http 3000
# Copy the HTTPS URL (e.g., https://abc123.ngrok.io)
Step 4: Create the Webhook via API
Use the Trello REST API to create your webhook:
cURL Example:
curl -X POST "https://api.trello.com/1/webhooks" \
-H "Content-Type: application/json" \
-d '{
"key": "YOUR_API_KEY",
"token": "YOUR_TOKEN",
"description": "Production Board Monitor",
"callbackURL": "https://yourapp.com/webhooks/trello",
"idModel": "board_jkl012"
}'
Node.js Example:
const axios = require('axios');
async function createTrelloWebhook() {
try {
const response = await axios.post('https://api.trello.com/1/webhooks', {
key: process.env.TRELLO_API_KEY,
token: process.env.TRELLO_TOKEN,
description: 'Production Board Monitor',
callbackURL: 'https://yourapp.com/webhooks/trello',
idModel: 'board_jkl012'
});
console.log('Webhook created:', response.data);
console.log('Webhook ID:', response.data.id);
// IMPORTANT: Store webhook ID for later management
return response.data.id;
} catch (error) {
console.error('Failed to create webhook:', error.response.data);
}
}
createTrelloWebhook();
Python Example:
import requests
import os
def create_trello_webhook():
url = 'https://api.trello.com/1/webhooks'
payload = {
'key': os.environ['TRELLO_API_KEY'],
'token': os.environ['TRELLO_TOKEN'],
'description': 'Production Board Monitor',
'callbackURL': 'https://yourapp.com/webhooks/trello',
'idModel': 'board_jkl012'
}
response = requests.post(url, json=payload)
if response.status_code == 200:
webhook_data = response.json()
print(f"Webhook created: {webhook_data}")
print(f"Webhook ID: {webhook_data['id']}")
return webhook_data['id']
else:
print(f"Failed to create webhook: {response.json()}")
webhook_id = create_trello_webhook()
Important Notes:
- Trello immediately sends a HEAD request to your callback URL to verify it's accessible
- If the HEAD request fails, webhook creation will fail with an error
- The webhook becomes active immediately upon successful creation
- Store the returned webhook ID for later management (updating, deleting, checking status)
Step 5: Verify Webhook Creation
Check that your webhook was created successfully:
# List all webhooks for your token
curl "https://api.trello.com/1/tokens/YOUR_TOKEN/webhooks?key=YOUR_API_KEY"
# Get specific webhook details
curl "https://api.trello.com/1/webhooks/WEBHOOK_ID?key=YOUR_API_KEY&token=YOUR_TOKEN"
Successful webhook response:
{
"id": "webhook_abc123",
"description": "Production Board Monitor",
"idModel": "board_jkl012",
"callbackURL": "https://yourapp.com/webhooks/trello",
"active": true
}
The active: true field indicates the webhook is operational.
Managing Trello Webhooks
Update Webhook:
curl -X PUT "https://api.trello.com/1/webhooks/WEBHOOK_ID?key=API_KEY&token=TOKEN" \
-H "Content-Type: application/json" \
-d '{
"description": "Updated Description",
"callbackURL": "https://newurl.com/webhooks/trello"
}'
Delete Webhook:
curl -X DELETE "https://api.trello.com/1/webhooks/WEBHOOK_ID?key=API_KEY&token=TOKEN"
Check Webhook Status:
curl "https://api.trello.com/1/webhooks/WEBHOOK_ID?key=API_KEY&token=TOKEN"
If active: false, the webhook has been deactivated due to delivery failures and must be recreated.
Trello Webhook Events & Payloads
Trello sends webhook payloads for ALL actions on the subscribed model (board). You cannot filter event types during webhook creation—filter in your application by checking action.type.
Payload Structure Overview
Every Trello webhook payload follows this format:
{
"action": {
"id": "action_abc123",
"type": "createCard",
"date": "2025-01-24T12:00:00.000Z",
"memberCreator": {
"id": "member123",
"username": "johndoe",
"fullName": "John Doe",
"avatarUrl": "https://..."
},
"data": {
// Event-specific data varies by action type
}
},
"model": {
"id": "board_jkl012",
"name": "Sprint Planning"
}
}
Key Fields:
action.id- Unique identifier for this action (use for idempotency)action.type- Event type (createCard, updateCard, commentCard, etc.)action.date- ISO 8601 timestamp when action occurredaction.memberCreator- User who triggered the actionaction.data- Event-specific details (varies by type)model- The board/model the action occurred on
Common Event Types
| Action Type | Description | Typical Use Case |
|---|---|---|
createCard | New card created on the board | Track new tasks, trigger notifications |
updateCard | Card modified (name, description, list, etc.) | Monitor card movements, status changes |
commentCard | Comment added to a card | Track team communication, trigger alerts |
addMemberToCard | Member assigned to a card | Monitor task assignments, send notifications |
updateCheckItemStateOnCard | Checklist item completed/uncompleted | Track task progress, trigger workflows |
deleteCard | Card deleted from board | Archive data, log deletions |
addAttachmentToCard | File attached to a card | Download attachments, sync files |
createList | New list created on board | Track workflow changes |
updateList | List renamed or closed | Monitor board structure changes |
moveCardToBoard | Card moved to different board | Sync cross-board data |
Detailed Event Examples
Event: createCard
Description: A new card was created on the board.
Payload Structure:
{
"action": {
"id": "action_abc123",
"type": "createCard",
"date": "2025-01-24T12:00:00.000Z",
"memberCreator": {
"id": "member123",
"username": "johndoe",
"fullName": "John Doe",
"avatarUrl": "https://trello-members.s3.amazonaws.com/avatar.png"
},
"data": {
"card": {
"id": "card_def456",
"name": "Implement new feature",
"desc": "Add user authentication to the dashboard",
"due": null,
"idList": "list_ghi789",
"shortLink": "abc123"
},
"board": {
"id": "board_jkl012",
"name": "Sprint Planning",
"shortLink": "def456"
},
"list": {
"id": "list_ghi789",
"name": "To Do"
}
}
},
"model": {
"id": "board_jkl012",
"name": "Sprint Planning"
}
}
Key Fields:
action.data.card- Complete card details including name, description, due dateaction.data.list- The list where the card was createdaction.data.card.shortLink- Short URL identifier (use for card links)
Use Cases:
- Send Slack notification when high-priority cards are created
- Auto-assign cards to team members based on card name keywords
- Log new tasks in your project management system
Event: updateCard
Description: An existing card was modified (name changed, moved to different list, etc.).
Payload Structure:
{
"action": {
"id": "action_xyz789",
"type": "updateCard",
"date": "2025-01-24T13:30:00.000Z",
"memberCreator": {
"id": "member456",
"username": "janesmith",
"fullName": "Jane Smith"
},
"data": {
"old": {
"idList": "list_ghi789"
},
"card": {
"id": "card_def456",
"name": "Implement new feature",
"idList": "list_mno345",
"shortLink": "abc123"
},
"board": {
"id": "board_jkl012",
"name": "Sprint Planning"
},
"listAfter": {
"id": "list_mno345",
"name": "In Progress"
},
"listBefore": {
"id": "list_ghi789",
"name": "To Do"
}
}
},
"model": {
"id": "board_jkl012",
"name": "Sprint Planning"
}
}
Key Fields:
action.data.old- Previous values (only includes changed fields)action.data.listBefore- List card was in before updateaction.data.listAfter- List card moved to (if applicable)
Common Update Types:
- List changes (
idListchanged) - Card moved between lists - Name/description changes - Card details updated
- Due date changes - Deadline added/modified
- Label changes - Card categorization updated
Use Cases:
- Track workflow progression when cards move to "Done" list
- Send reminders when due dates are added
- Log card history for audit trails
Event: commentCard
Description: A comment was added to a card.
Payload Structure:
{
"action": {
"id": "action_comment123",
"type": "commentCard",
"date": "2025-01-24T14:15:00.000Z",
"memberCreator": {
"id": "member789",
"username": "bobwilson",
"fullName": "Bob Wilson"
},
"data": {
"text": "Great progress on this task! Let me know if you need any help.",
"card": {
"id": "card_def456",
"name": "Implement new feature",
"shortLink": "abc123"
},
"board": {
"id": "board_jkl012",
"name": "Sprint Planning"
}
}
},
"model": {
"id": "board_jkl012",
"name": "Sprint Planning"
}
}
Key Fields:
action.data.text- Full comment textaction.data.card- Card that was commented on
Use Cases:
- Send email notifications for @mentions in comments
- Extract action items from comments using AI/NLP
- Track team collaboration and communication patterns
Event: addMemberToCard
Description: A team member was assigned to a card.
Payload Structure:
{
"action": {
"id": "action_member456",
"type": "addMemberToCard",
"date": "2025-01-24T15:00:00.000Z",
"memberCreator": {
"id": "member123",
"username": "johndoe",
"fullName": "John Doe"
},
"data": {
"card": {
"id": "card_def456",
"name": "Implement new feature",
"shortLink": "abc123"
},
"board": {
"id": "board_jkl012",
"name": "Sprint Planning"
},
"member": {
"id": "member456",
"username": "janesmith",
"fullName": "Jane Smith"
}
}
},
"model": {
"id": "board_jkl012",
"name": "Sprint Planning"
}
}
Key Fields:
action.data.member- The member who was assigned to the cardaction.memberCreator- The person who made the assignment (may be different)
Use Cases:
- Notify assigned members via email or Slack
- Update workload tracking systems
- Trigger onboarding workflows for new tasks
Event: updateCheckItemStateOnCard
Description: A checklist item was checked or unchecked.
Payload Structure:
{
"action": {
"id": "action_check789",
"type": "updateCheckItemStateOnCard",
"date": "2025-01-24T16:00:00.000Z",
"memberCreator": {
"id": "member456",
"username": "janesmith",
"fullName": "Jane Smith"
},
"data": {
"checkItem": {
"id": "checkitem_abc123",
"name": "Write unit tests",
"state": "complete"
},
"card": {
"id": "card_def456",
"name": "Implement new feature",
"shortLink": "abc123"
},
"board": {
"id": "board_jkl012",
"name": "Sprint Planning"
},
"checklist": {
"id": "checklist_xyz789",
"name": "Development Tasks"
}
}
},
"model": {
"id": "board_jkl012",
"name": "Sprint Planning"
}
}
Key Fields:
action.data.checkItem- The checklist item with its state (complete/incomplete)action.data.checklist- Parent checklist containing the item
Use Cases:
- Track task completion percentages
- Trigger workflows when all checklist items are complete
- Monitor developer progress on subtasks
Webhook Signature Verification
Why Signature Verification Matters
Without signature verification, attackers could:
- Spoof webhook requests by sending fake action events to your endpoint
- Manipulate data by injecting false card updates or deletions
- Trigger unintended actions like creating duplicate tasks or sending false notifications
- Exploit business logic by fabricating member assignments or checklist completions
Trello's unique HMAC-SHA1 signature (body + callback URL) cryptographically proves that webhook requests genuinely originated from Trello's servers and are intended for your specific endpoint.
Trello's Signature Method
Algorithm: HMAC-SHA1 with base64 encoding
Header Name: X-Trello-Webhook
What's Signed: Trello creates a signature by:
- Concatenating the raw request body bytes + callback URL string
- Computing HMAC-SHA1 hash using your app secret as the key
- Base64-encoding the hash for transmission
Formula:
data_to_sign = request_body + callback_url
signature = base64(HMAC-SHA1(data_to_sign, app_secret))
Example:
Request body: {"action":{"type":"createCard"}}
Callback URL: https://yourapp.com/webhooks/trello
App secret: your_app_secret_here
Data to sign: {"action":{"type":"createCard"}}https://yourapp.com/webhooks/trello
HMAC-SHA1 hash: (binary hash)
Base64 encoded: dGVzdHNpZ25hdHVyZQ== (sent in X-Trello-Webhook header)
Step-by-Step Verification Process
- Extract the signature from the
X-Trello-Webhookheader - Get the raw request body (must be unmodified bytes, not parsed JSON)
- Retrieve your callback URL from configuration (the URL you registered the webhook with)
- Concatenate body + callback URL in exact order (body first, then URL)
- Compute HMAC-SHA1 using your app secret as the key
- Base64 encode the computed hash
- Compare signatures using constant-time comparison to prevent timing attacks
Code Examples
Node.js / Express
const express = require('express');
const crypto = require('crypto');
const bodyParser = require('body-parser');
const app = express();
const APP_SECRET = process.env.TRELLO_APP_SECRET;
const CALLBACK_URL = process.env.TRELLO_CALLBACK_URL; // https://yourapp.com/webhooks/trello
// Verification function
const verifyTrelloSignature = (body, callbackUrl, signature, secret) => {
// Concatenate body + callback URL
const dataToSign = body + callbackUrl;
// Compute HMAC-SHA1
const expectedSignature = crypto
.createHmac('sha1', secret)
.update(dataToSign, 'utf8')
.digest('base64');
// Constant-time comparison
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
};
// HEAD request handler (required for webhook creation)
app.head('/webhooks/trello', (req, res) => {
res.status(200).send();
});
// Webhook endpoint with raw body parsing
app.post('/webhooks/trello',
bodyParser.raw({ type: 'application/json' }),
(req, res) => {
try {
// Extract signature from header
const signature = req.get('X-Trello-Webhook');
if (!signature) {
console.error('Missing Trello webhook signature');
return res.status(403).json({ error: 'Missing signature' });
}
// Get raw body as string
const rawBody = req.body.toString('utf8');
// Verify signature
const isValid = verifyTrelloSignature(
rawBody,
CALLBACK_URL,
signature,
APP_SECRET
);
if (!isValid) {
console.error('Invalid Trello webhook signature');
return res.status(403).json({ error: 'Invalid signature' });
}
// Parse payload after verification
const payload = JSON.parse(rawBody);
console.log(`Trello webhook: ${payload.action.type} on card ${payload.action.data.card?.name || 'unknown'}`);
// Return 200 immediately
res.status(200).send();
// Process async (see Implementation Example section)
processWebhookAsync(payload);
} catch (error) {
console.error('Trello webhook processing error:', error);
res.status(500).json({ error: 'Processing failed' });
}
}
);
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Trello webhook server listening on port ${PORT}`);
});
Python / Flask
import hmac
import hashlib
import base64
from flask import Flask, request, jsonify
app = Flask(__name__)
APP_SECRET = 'your_app_secret_here'
CALLBACK_URL = 'https://yourapp.com/webhooks/trello'
def verify_trello_signature(body, callback_url, signature, secret):
"""Verify Trello webhook signature"""
# Concatenate body + callback URL
data_to_sign = body + callback_url
# Compute HMAC-SHA1
expected_signature = base64.b64encode(
hmac.new(
secret.encode('utf-8'),
data_to_sign.encode('utf-8'),
hashlib.sha1
).digest()
).decode('utf-8')
# Constant-time comparison
return hmac.compare_digest(signature, expected_signature)
@app.route('/webhooks/trello', methods=['HEAD'])
def trello_webhook_head():
"""Handle HEAD request for webhook verification"""
return '', 200
@app.route('/webhooks/trello', methods=['POST'])
def trello_webhook():
try:
# Extract signature from header
signature = request.headers.get('X-Trello-Webhook')
if not signature:
print('Missing Trello webhook signature')
return jsonify({'error': 'Missing signature'}), 403
# Get raw body
raw_body = request.get_data(as_text=True)
# Verify signature
is_valid = verify_trello_signature(
raw_body,
CALLBACK_URL,
signature,
APP_SECRET
)
if not is_valid:
print('Invalid Trello webhook signature')
return jsonify({'error': 'Invalid signature'}), 403
# Parse payload after verification
payload = request.get_json()
action_type = payload['action']['type']
card_name = payload['action']['data'].get('card', {}).get('name', 'unknown')
print(f'Trello webhook: {action_type} on card {card_name}')
# Return 200 immediately
return '', 200
except Exception as error:
print(f'Trello webhook processing error: {error}')
return jsonify({'error': 'Processing failed'}), 500
if __name__ == '__main__':
app.run(port=3000, debug=False)
PHP
<?php
// Get app secret and callback URL from environment
$appSecret = getenv('TRELLO_APP_SECRET');
$callbackUrl = getenv('TRELLO_CALLBACK_URL'); // https://yourapp.com/webhooks/trello
// Handle HEAD request for webhook verification
if ($_SERVER['REQUEST_METHOD'] === 'HEAD') {
http_response_code(200);
exit;
}
// Extract signature from header
$signature = $_SERVER['HTTP_X_TRELLO_WEBHOOK'] ?? '';
if (empty($signature)) {
error_log('Missing Trello webhook signature');
http_response_code(403);
die(json_encode(['error' => 'Missing signature']));
}
// Get raw POST body (MUST be raw bytes, not parsed)
$rawBody = file_get_contents('php://input');
// Concatenate body + callback URL
$dataToSign = $rawBody . $callbackUrl;
// Compute HMAC-SHA1
$expectedSignature = base64_encode(
hash_hmac('sha1', $dataToSign, $appSecret, true)
);
// Constant-time comparison
if (!hash_equals($signature, $expectedSignature)) {
error_log('Invalid Trello webhook signature');
http_response_code(403);
die(json_encode(['error' => 'Invalid signature']));
}
// Parse payload after verification
$payload = json_decode($rawBody, true);
$actionType = $payload['action']['type'];
$cardName = $payload['action']['data']['card']['name'] ?? 'unknown';
error_log("Trello webhook: {$actionType} on card {$cardName}");
// Return 200 immediately
http_response_code(200);
// Process async (implement your queue logic here)
?>
Common Verification Errors
- ❌ Forgetting callback URL: Signature won't match without including it
- ✅ Concatenate body + callback URL before hashing
- ❌ Parsing JSON before verification: Body modified, signature breaks
- ✅ Use raw body parser, verify first, then parse JSON
- ❌ Wrong callback URL: Using different URL than webhook was registered with
- ✅ Store callback URL used during webhook creation
- ❌ Wrong algorithm: Using SHA-256 instead of SHA-1
- ✅ Trello uses HMAC-SHA1, not SHA-256
- ❌ Comparing signatures with
==: Vulnerable to timing attacks- ✅ Use constant-time comparison functions
- ❌ Encoding mismatches: Incorrect base64 encoding/decoding
- ✅ Ensure base64 encoding matches Trello's format
Testing Trello Webhooks
Local Development Challenges
Testing Trello webhooks during development presents several obstacles:
- Trello can't reach
localhost: Webhook URLs must be publicly accessible - HTTPS requirement: Trello only sends to secure HTTPS endpoints
- HEAD request verification: Endpoints must respond to HEAD before webhook creation
- Signature generation complexity: Manually creating valid signatures with body + callbackURL concatenation is error-prone
Solution 1: ngrok for Local Testing
ngrok creates a secure tunnel from a public URL to your local development server.
Setup Steps:
# Install ngrok (macOS)
brew install ngrok
# Or download from ngrok.com for other platforms
# Start your local webhook server on port 3000
node server.js
# In another terminal, create ngrok tunnel
ngrok http 3000
# ngrok output will show:
# Forwarding https://abc123def456.ngrok.io -> http://localhost:3000
Create Webhook with ngrok URL:
curl -X POST "https://api.trello.com/1/webhooks" \
-H "Content-Type: application/json" \
-d '{
"key": "YOUR_API_KEY",
"token": "YOUR_TOKEN",
"description": "Dev Testing",
"callbackURL": "https://abc123def456.ngrok.io/webhooks/trello",
"idModel": "YOUR_BOARD_ID"
}'
Trigger Test Events:
- Open your Trello board in the browser
- Create a card, move it, add a comment, etc.
- Your local server will receive webhook POSTs immediately
- View all HTTP traffic in ngrok web interface at
http://127.0.0.1:4040
ngrok Pro Tips:
- Use
ngrok http 3000 --log=stdoutto see all HTTP traffic in terminal - ngrok URLs change on restart (use paid plan for static URLs)
- Set environment variable for callback URL to avoid hardcoding
Solution 2: Webhook Payload Generator Tool
For testing without creating real webhooks or exposing your local environment, use our specialized tool:
Visit Webhook Payload Generator
Testing Workflow:
- Select Provider: Choose "Trello" from the dropdown
- Choose Event Type: Select action (createCard, updateCard, commentCard, etc.)
- Customize Payload: Edit card names, board IDs, member info
- Enter Callback URL: Set your registered webhook callback URL
- Generate Signature: Tool creates valid HMAC-SHA1 signature (body + callbackURL)
- Copy & Send: Use curl or Postman to POST to
http://localhost:3000/webhooks/trello
Example cURL Command:
curl -X POST http://localhost:3000/webhooks/trello \
-H "Content-Type: application/json" \
-H "X-Trello-Webhook: <generated-signature>" \
-d '{"action":{"id":"action_abc123","type":"createCard","date":"2025-01-24T12:00:00.000Z","memberCreator":{"id":"member123","username":"johndoe","fullName":"John Doe"},"data":{"card":{"id":"card_def456","name":"Test Card","shortLink":"abc123"},"board":{"id":"board_jkl012","name":"Test Board"}}},"model":{"id":"board_jkl012","name":"Test Board"}}'
Benefits of Webhook Payload Generator:
- ✅ Test unique body + callbackURL signature logic
- ✅ No Trello API access needed
- ✅ Customize payloads for edge cases
- ✅ Test error handling with malformed data
- ✅ No webhook creation/deletion overhead
- ✅ Instant testing without waiting for real Trello events
Trello's Webhook Verification
When you create a webhook, Trello sends a HEAD request to verify your endpoint:
HEAD /webhooks/trello HTTP/1.1
Host: yourapp.com
Your endpoint must return 200 status or webhook creation will fail.
Test HEAD response:
curl -I -X HEAD https://yourapp.com/webhooks/trello
# Should return:
# HTTP/1.1 200 OK
Testing Checklist
Before deploying to production, verify:
- HEAD request returns 200 (test with curl -I -X HEAD)
- Signature verification passes with valid Trello signatures (including callbackURL)
- Endpoint returns 200 for POST requests within 30 seconds
- Callback URL stored correctly in environment/config
- All action types handled (or safely ignored)
- Idempotent processing handles duplicate action IDs gracefully
- Error handling catches malformed payloads without crashing
- Async processing queues events instead of blocking response
- Logging captures action details for debugging
- Monitoring alerts on signature verification failures
Test Each Action Type:
Use the Webhook Payload Generator to send:
- ✅
createCardevent - ✅
updateCardevent (list change) - ✅
commentCardevent - ✅
addMemberToCardevent - ✅
updateCheckItemStateOnCardevent - ✅
deleteCardevent
Verify your application responds correctly to each scenario.
Implementation Example
Building a production-ready Trello webhook endpoint requires proper error handling, async processing, idempotency checks, and monitoring.
Requirements for Production Endpoints
- Respond to HEAD requests: Required for webhook creation verification
- Respond within 30 seconds: Trello doesn't have explicit timeout, but be fast
- Return 200 status codes: Indicates successful receipt
- Process asynchronously: Queue events for background processing to respond quickly
- Implement idempotency: Use
action.idto prevent duplicate processing - Handle all action types: Filter or process based on
action.type - Log comprehensively: Track all events for debugging and reconciliation
Complete Node.js Production Example
This example uses Express, Bull queue for async processing, and Prisma for database operations.
const express = require('express');
const bodyParser = require('body-parser');
const crypto = require('crypto');
const Queue = require('bull');
const { PrismaClient } = require('@prisma/client');
const app = express();
const prisma = new PrismaClient();
// Configuration
const APP_SECRET = process.env.TRELLO_APP_SECRET;
const CALLBACK_URL = process.env.TRELLO_CALLBACK_URL;
// Create Bull queue for async webhook processing
const webhookQueue = new Queue('trello-webhooks', {
redis: {
host: process.env.REDIS_HOST || 'localhost',
port: process.env.REDIS_PORT || 6379
}
});
// Signature verification helper
const verifyTrelloSignature = (body, callbackUrl, signature, secret) => {
const dataToSign = body + callbackUrl;
const expectedSignature = crypto
.createHmac('sha1', secret)
.update(dataToSign, 'utf8')
.digest('base64');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
};
// HEAD request handler (required for webhook creation)
app.head('/webhooks/trello', (req, res) => {
console.log('[Trello] HEAD request received');
res.status(200).send();
});
// Trello webhook endpoint
app.post('/webhooks/trello',
bodyParser.raw({ type: 'application/json' }),
async (req, res) => {
const startTime = Date.now();
try {
// 1. Extract signature
const signature = req.get('X-Trello-Webhook');
if (!signature) {
console.error('[Trello] Missing signature header');
return res.status(403).json({ error: 'Missing signature' });
}
// 2. Get raw body
const rawBody = req.body.toString('utf8');
// 3. Verify signature
const isValid = verifyTrelloSignature(
rawBody,
CALLBACK_URL,
signature,
APP_SECRET
);
if (!isValid) {
console.error('[Trello] Invalid signature');
return res.status(403).json({ error: 'Invalid signature' });
}
// 4. Parse payload
const payload = JSON.parse(rawBody);
const actionId = payload.action.id;
const actionType = payload.action.type;
console.log(`[Trello] Received ${actionType} action ${actionId}`);
// 5. Check if already processed (idempotency)
const exists = await prisma.trelloWebhookEvent.findUnique({
where: { actionId }
});
if (exists) {
console.log(`[Trello] Action ${actionId} already processed, skipping`);
return res.status(200).send(); // Still return 200 to avoid retries
}
// 6. Queue for async processing
await webhookQueue.add({
actionId,
actionType,
actionDate: payload.action.date,
rawPayload: payload
}, {
attempts: 3,
backoff: {
type: 'exponential',
delay: 2000
}
});
// 7. Return 200 immediately
const processingTime = Date.now() - startTime;
console.log(`[Trello] Queued in ${processingTime}ms`);
res.status(200).send();
} catch (error) {
console.error('[Trello] Webhook processing error:', error);
// Return 500 to indicate processing failure
res.status(500).json({ error: 'Internal server error' });
}
}
);
// Process webhooks from queue
webhookQueue.process(async (job) => {
const { actionId, actionType, actionDate, rawPayload } = job.data;
try {
console.log(`[Queue] Processing ${actionType} action ${actionId}`);
// Mark as processing in database
await prisma.trelloWebhookEvent.create({
data: {
actionId,
actionType,
actionDate: new Date(actionDate),
status: 'processing',
rawPayload: JSON.stringify(rawPayload)
}
});
// Handle different action types
switch (actionType) {
case 'createCard':
await handleCreateCard(rawPayload);
break;
case 'updateCard':
await handleUpdateCard(rawPayload);
break;
case 'commentCard':
await handleCommentCard(rawPayload);
break;
case 'addMemberToCard':
await handleAddMemberToCard(rawPayload);
break;
case 'updateCheckItemStateOnCard':
await handleCheckItemUpdate(rawPayload);
break;
case 'deleteCard':
await handleDeleteCard(rawPayload);
break;
default:
console.log(`[Queue] Unhandled action type: ${actionType}`);
}
// Mark as completed
await prisma.trelloWebhookEvent.update({
where: { actionId },
data: {
status: 'completed',
processedAt: new Date()
}
});
console.log(`[Queue] Completed ${actionType} action ${actionId}`);
} catch (error) {
console.error(`[Queue] Failed to process action ${actionId}:`, error);
// Mark as failed
await prisma.trelloWebhookEvent.update({
where: { actionId },
data: {
status: 'failed',
errorMessage: error.message,
failedAt: new Date()
}
});
throw error; // Trigger Bull queue retry
}
});
// Business logic handlers
async function handleCreateCard(payload) {
const card = payload.action.data.card;
const list = payload.action.data.list;
const creator = payload.action.memberCreator;
// Store card in your database
await prisma.card.create({
data: {
trelloCardId: card.id,
name: card.name,
description: card.desc || '',
listId: list.id,
listName: list.name,
createdBy: creator.fullName,
createdAt: new Date(payload.action.date)
}
});
console.log(`[Handler] Created card: ${card.name} in ${list.name}`);
// Trigger notifications
if (card.name.includes('[URGENT]')) {
await sendUrgentCardNotification(card, creator);
}
}
async function handleUpdateCard(payload) {
const card = payload.action.data.card;
const oldValues = payload.action.data.old;
const listAfter = payload.action.data.listAfter;
const listBefore = payload.action.data.listBefore;
// Check if card moved to different list
if (oldValues.idList && listAfter) {
console.log(`[Handler] Card ${card.name} moved: ${listBefore.name} → ${listAfter.name}`);
// Update card list in database
await prisma.card.updateMany({
where: { trelloCardId: card.id },
data: {
listId: listAfter.id,
listName: listAfter.name,
lastMovedAt: new Date(payload.action.date)
}
});
// Trigger workflow if moved to "Done"
if (listAfter.name === 'Done') {
await handleCardCompleted(card);
}
}
}
async function handleCommentCard(payload) {
const comment = payload.action.data.text;
const card = payload.action.data.card;
const commenter = payload.action.memberCreator;
// Store comment
await prisma.comment.create({
data: {
cardId: card.id,
text: comment,
author: commenter.fullName,
createdAt: new Date(payload.action.date)
}
});
console.log(`[Handler] Comment on ${card.name}: ${comment}`);
// Check for @mentions
if (comment.includes('@')) {
await handleMentions(comment, card);
}
}
async function handleAddMemberToCard(payload) {
const card = payload.action.data.card;
const member = payload.action.data.member;
const assigner = payload.action.memberCreator;
console.log(`[Handler] ${member.fullName} assigned to ${card.name} by ${assigner.fullName}`);
// Notify assigned member
await sendAssignmentNotification(member, card, assigner);
}
async function handleCheckItemUpdate(payload) {
const checkItem = payload.action.data.checkItem;
const card = payload.action.data.card;
console.log(`[Handler] Checklist item "${checkItem.name}" marked as ${checkItem.state} on ${card.name}`);
// Update checklist progress
await updateChecklistProgress(card.id);
}
async function handleDeleteCard(payload) {
const card = payload.action.data.card;
console.log(`[Handler] Card deleted: ${card.name}`);
// Archive or soft-delete in database
await prisma.card.updateMany({
where: { trelloCardId: card.id },
data: {
deletedAt: new Date(payload.action.date)
}
});
}
// Helper functions (implement based on your needs)
async function sendUrgentCardNotification(card, creator) {
console.log(`[Notification] URGENT card created: ${card.name} by ${creator.fullName}`);
// Send Slack/email notification
}
async function handleCardCompleted(card) {
console.log(`[Workflow] Card completed: ${card.name}`);
// Trigger completion workflow
}
async function handleMentions(comment, card) {
const mentions = comment.match(/@\w+/g);
console.log(`[Mentions] Found mentions in comment on ${card.name}: ${mentions}`);
// Send notifications to mentioned users
}
async function sendAssignmentNotification(member, card, assigner) {
console.log(`[Notification] Notifying ${member.fullName} of assignment to ${card.name}`);
// Send email/Slack notification
}
async function updateChecklistProgress(cardId) {
console.log(`[Progress] Updating checklist progress for card ${cardId}`);
// Calculate and store checklist completion percentage
}
// Health check endpoint
app.get('/health', (req, res) => {
res.json({
status: 'ok',
timestamp: new Date().toISOString()
});
});
// Graceful shutdown
process.on('SIGTERM', async () => {
console.log('SIGTERM received, closing server gracefully');
await webhookQueue.close();
await prisma.$disconnect();
process.exit(0);
});
// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Trello webhook server listening on port ${PORT}`);
console.log(`Endpoint: POST /webhooks/trello`);
console.log(`Callback URL: ${CALLBACK_URL}`);
});
Database Schema Example (Prisma)
model TrelloWebhookEvent {
id String @id @default(cuid())
actionId String @unique // Trello action.id
actionType String // createCard, updateCard, etc.
actionDate DateTime
status String // processing, completed, failed
rawPayload String @db.Text
errorMessage String?
processedAt DateTime?
failedAt DateTime?
createdAt DateTime @default(now())
@@index([actionType, createdAt])
}
model Card {
id String @id @default(cuid())
trelloCardId String @unique
name String
description String @db.Text
listId String
listName String
createdBy String
createdAt DateTime
lastMovedAt DateTime?
deletedAt DateTime?
@@index([listId, deletedAt])
}
model Comment {
id String @id @default(cuid())
cardId String
text String @db.Text
author String
createdAt DateTime
@@index([cardId, createdAt])
}
Best Practices
Security
- ✅ Always verify signatures: Include callback URL in verification
- ✅ Store secrets securely: Environment variables, never commit to git
- ✅ Use HTTPS only: Trello requires HTTPS endpoints
- ✅ Validate callback URL: Store and verify it matches webhook registration
- ✅ Implement rate limiting: Protect against abuse
- ✅ Monitor signature failures: Alert on sudden increase in invalid signatures
- ✅ Rotate secrets periodically: Update app secrets and recreate webhooks
- ✅ Isolate webhook endpoints: Don't expose webhook paths in public documentation
Performance
- ✅ Respond within 30 seconds: Use async processing for anything longer
- ✅ Return 200 immediately: Acknowledge receipt, process later
- ✅ Use queue systems: Redis/Bull, RabbitMQ, AWS SQS for async processing
- ✅ Implement connection pooling: Database connections should be pooled
- ✅ Monitor processing times: Alert if queue depth grows
- ✅ Handle HEAD requests efficiently: Simple 200 response, no processing
Reliability
- ✅ Implement idempotency: Store
action.idbefore processing to detect duplicates - ✅ Handle all action types: Filter or process based on
action.type - ✅ Log all events: Comprehensive logging enables debugging
- ✅ Monitor webhook status: Check
activefield, recreate if inactive - ✅ Test failure scenarios: Simulate errors, timeouts, malformed payloads
- ✅ Implement dead letter queues: Store failed events for manual review
- ✅ Alert on webhook inactivity: Monitor for missing webhooks
Monitoring
- ✅ Track webhook delivery rate: Alert if no webhooks received for X minutes
- ✅ Monitor signature verification: Alert on failures
- ✅ Log unique action IDs: Use
action.idfor tracing events - ✅ Track action type distribution: Sudden changes warrant investigation
- ✅ Monitor queue depth: Growing queue indicates bottleneck
- ✅ Set up health checks: Ensure endpoint is reachable and responsive
Trello-Specific Best Practices
Webhook Lifecycle Management:
- Trello webhooks can become inactive after repeated delivery failures
- Monitor the
activefield by querying webhook details periodically - Automatically recreate webhooks when they become inactive
- Store webhook IDs in your database for management
Action Filtering:
- Trello sends ALL actions for the subscribed model (board)
- Filter unwanted action types in your application, not at webhook creation
- Common filters: ignore
updateBoard,addLabelToCardunless needed - Use switch/case statements for action type routing
Callback URL Management:
- Store callback URL in environment variables alongside app secret
- Use the same callback URL for verification that was used during webhook creation
- Document callback URLs for each environment (dev, staging, production)
- Update webhooks if callback URL changes (delete old, create new)
Rate Limits:
- Trello doesn't publish explicit webhook rate limits
- API rate limit: 300 requests per 10 seconds per API key
- Webhook creation counts against API rate limit
- Use exponential backoff when recreating webhooks
Common Issues & Troubleshooting
Issue 1: Signature Verification Failing
Symptoms:
- 403 errors in your application logs
- "Invalid signature" messages
- Webhooks not processing despite Trello sending them
Causes & Solutions:
❌ Callback URL mismatch: Using different URL than webhook was registered with
✅ Solution: Verify your CALLBACK_URL environment variable matches exactly what you used during webhook creation (including trailing slashes, http/https, etc.)
# Check your webhook details
curl "https://api.trello.com/1/webhooks/WEBHOOK_ID?key=API_KEY&token=TOKEN"
# Verify callbackURL field matches your environment variable
❌ Parsing JSON before verification: Body modified, signature won't match
✅ Solution: Use raw body parser for webhook endpoint, verify signature before calling JSON.parse()
// Wrong: JSON parsed before verification
app.use(express.json()); // Applied to ALL routes
app.post('/webhooks/trello', (req, res) => {
const signature = req.get('X-Trello-Webhook');
const body = JSON.stringify(req.body); // Body already parsed!
// Signature verification will fail
});
// Correct: Raw body for webhook endpoint only
app.post('/webhooks/trello', express.raw({type: 'application/json'}), (req, res) => {
const rawBody = req.body.toString('utf8'); // Raw string
// Verify signature with raw bytes
});
❌ Using SHA-256 instead of SHA-1: Wrong algorithm
✅ Solution: Trello uses HMAC-SHA1, not HMAC-SHA256
// Wrong algorithm
const hash = crypto.createHmac('sha256', secret); // ❌
// Correct algorithm
const hash = crypto.createHmac('sha1', secret); // ✅
Issue 2: Webhook Creation Failing
Symptoms:
- API returns error when creating webhook
- "Webhook callback URL is unreachable" error
- Webhook creation succeeds but never sends events
Causes & Solutions:
❌ Endpoint not responding to HEAD requests: Trello verifies before creating
✅ Solution: Implement HEAD request handler that returns 200
app.head('/webhooks/trello', (req, res) => {
res.status(200).send();
});
❌ Endpoint not publicly accessible: Using localhost or firewall blocking
✅ Solution: Use ngrok for local testing or deploy to publicly accessible server
# Test endpoint accessibility
curl -I -X HEAD https://yourapp.com/webhooks/trello
# Should return HTTP/1.1 200 OK
❌ SSL certificate issues: Expired or invalid certificate
✅ Solution: Verify SSL certificate with:
curl -vI https://yourapp.com/webhooks/trello
# Check for SSL errors in output
Issue 3: Webhook Becomes Inactive
Symptoms:
- Webhook stops receiving events
active: falsein webhook details- No errors in application logs
Cause:
Trello deactivates webhooks after consecutive delivery failures (endpoint returning errors or timing out).
Solution:
Monitor webhook status and recreate when inactive:
async function checkWebhookHealth(webhookId) {
const response = await axios.get(
`https://api.trello.com/1/webhooks/${webhookId}`,
{
params: {
key: process.env.TRELLO_API_KEY,
token: process.env.TRELLO_TOKEN
}
}
);
if (!response.data.active) {
console.error('Webhook inactive, recreating...');
await recreateWebhook(webhookId);
}
}
// Run health check periodically (e.g., every hour)
setInterval(() => checkWebhookHealth('YOUR_WEBHOOK_ID'), 3600000);
Issue 4: Missing Webhooks
Symptoms:
- Expected webhooks not arriving
- Gaps in event data despite Trello actions occurring
- Webhook shows
active: truebut no POSTs received
Causes & Solutions:
❌ Firewall blocking: Corporate firewall blocking inbound HTTPS
✅ Solution: Check firewall rules, allowlist Trello IPs (Trello doesn't publish IP ranges, monitor logs for source IPs)
❌ Wrong model ID: Webhook subscribed to different board
✅ Solution: Verify idModel in webhook details matches your intended board ID
curl "https://api.trello.com/1/webhooks/WEBHOOK_ID?key=API_KEY&token=TOKEN"
# Check idModel field
Debugging Checklist
- Verify webhook exists (GET /1/webhooks/{id})
- Check webhook is active (
active: truein webhook details) - Test HEAD request (curl -I -X HEAD your-endpoint)
- Verify signature with Webhook Payload Generator tool
- Check application logs for errors during processing
- Confirm SSL certificate is valid (not expired)
- Test callback URL accessibility from external server (not your network)
- Verify model ID is correct (board you're monitoring)
- Check rate limiting isn't blocking requests
- Monitor queue depth if using async processing
Frequently Asked Questions
Q: Can I subscribe to specific event types only?
A: No, Trello webhooks send ALL actions for the subscribed model (board). You must filter event types in your application by checking action.type in the payload and ignoring unwanted events.
Q: What happens if my endpoint is down?
A: Unlike some webhook providers, Trello does not implement automatic retry logic. After multiple consecutive delivery failures, Trello will mark the webhook as active: false. You must monitor webhook health and recreate webhooks programmatically when they become inactive.
Q: Can I use the same webhook for multiple boards?
A: No, each webhook subscribes to a single idModel (typically a board ID). To monitor multiple boards, create separate webhooks for each board, each with its own webhook ID.
Q: How long do Trello webhooks stay active?
A: Webhooks remain active indefinitely unless they fail repeatedly (causing deactivation) or the associated model (board) is deleted. Monitor the active field and recreate webhooks as needed.
Q: Why is the signature verification unique?
A: Trello concatenates the request body + callback URL before signing. This prevents signature replay attacks—even if an attacker captures a webhook payload and signature, they cannot replay it to a different endpoint because the callback URL is part of the signed data.
Q: How do I test webhooks without ngrok?
A: Use our Webhook Payload Generator to create properly signed test payloads with the unique body + callbackURL concatenation. This lets you test signature verification logic without exposing your local environment or creating real Trello webhooks.
Q: What's the difference between API key and app secret?
A: The API key is a public identifier used when making API requests to Trello. The app secret is a private key used to verify webhook signatures. Both are obtained from https://trello.com/app-key. Never share your app secret publicly.
Q: Can I filter which boards a webhook monitors?
A: Webhooks subscribe to a single model ID (typically a board). To monitor specific boards only, create separate webhooks with different idModel values for each board you want to track.
Q: Why do I need to handle HEAD requests?
A: Trello sends a HEAD request to your callback URL before creating the webhook to verify the endpoint is accessible. If your endpoint doesn't respond with 200 to HEAD requests, webhook creation will fail.
Next Steps & Resources
Try It Yourself
Ready to implement Trello webhooks? Follow these steps:
- Get credentials from https://trello.com/app-key (API key, app secret, token)
- Test with our tool: Visit the Webhook Payload Generator to create signed test payloads
- Implement signature verification using code examples from this guide (Node.js, Python, or PHP)
- Create webhook via API with your board ID and callback URL
- Deploy to production with async processing, idempotency checks, and monitoring
Additional Resources
Trello Official Documentation:
- Webhooks Guide - Official webhook documentation
- REST API Reference - API endpoints for webhook management
- Getting Started with Trello API - API basics and authentication
Related Guides on InventiveHQ:
- Webhooks Explained: Complete Guide - Comprehensive webhook fundamentals
- Webhook Signature Verification Guide - Deep dive into signature algorithms
Testing & Development Tools:
- Webhook Payload Generator - Create signed test payloads for Trello (includes unique body + callbackURL)
- ngrok - Expose localhost for webhook testing
Need Help?
- Test your integration: Use our Webhook Payload Generator to validate signature verification
- Community support: Ask questions in Atlassian Community Forums
- Get expert guidance: Contact InventiveHQ for webhook integration consulting
Conclusion
Trello webhooks provide a powerful real-time notification system that enables seamless integration between Trello boards and your custom applications. By following this guide, you now know how to:
- ✅ Create Trello webhooks programmatically via the REST API
- ✅ Verify webhook signatures securely using the unique body + callbackURL concatenation with HMAC-SHA1
- ✅ Implement production-ready webhook endpoints with async processing
- ✅ Handle all action types from card creation to checklist updates
- ✅ Test webhooks effectively using ngrok or our payload generator tool
- ✅ Troubleshoot common issues like signature failures and inactive webhooks
Remember these key principles:
- Include callback URL in signature verification - Trello's unique approach prevents replay attacks
- Respond to HEAD requests - Required for webhook creation verification
- Process asynchronously - Queue events for background processing for reliability at scale
- Implement idempotency - Use
action.idto detect and handle duplicate events gracefully - Monitor webhook health - Recreate webhooks when they become inactive
Trello's webhook system combines simplicity (straightforward JSON payloads), security (unique signature method with callback URL), and flexibility (40+ action types) to give you complete visibility into board activity. Whether you're syncing Trello with your CRM, automating workflows when cards move, or building custom analytics dashboards, webhooks provide the real-time data pipeline your application needs.
Start building with Trello webhooks today, and use our Webhook Payload Generator to test your integration without creating real webhooks or exposing your development environment.
Have questions or run into issues? Visit the Atlassian Community Forums or contact our team for webhook integration assistance.
Sources: