Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:52:47 +08:00
commit 282d2cbece
17 changed files with 1973 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
{
"name": "webhook-handler-creator",
"description": "Create secure webhook endpoints with signature verification and retry logic",
"version": "1.0.0",
"author": {
"name": "Jeremy Longshore",
"email": "[email protected]"
},
"skills": [
"./skills"
],
"commands": [
"./commands"
]
}

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# webhook-handler-creator
Create secure webhook endpoints with signature verification and retry logic

File diff suppressed because it is too large Load Diff

97
plugin.lock.json Normal file
View File

@@ -0,0 +1,97 @@
{
"$schema": "internal://schemas/plugin.lock.v1.json",
"pluginId": "gh:jeremylongshore/claude-code-plugins-plus:plugins/api-development/webhook-handler-creator",
"normalized": {
"repo": null,
"ref": "refs/tags/v20251128.0",
"commit": "a0e5a661bb6e68164c56d97190c482af0228432d",
"treeHash": "9fe2ac6f8302ba61bc68427db8c4de6513a4ac949c5c55d8b48beb9b138712f6",
"generatedAt": "2025-11-28T10:18:52.349343Z",
"toolVersion": "publish_plugins.py@0.2.0"
},
"origin": {
"remote": "git@github.com:zhongweili/42plugin-data.git",
"branch": "master",
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
},
"manifest": {
"name": "webhook-handler-creator",
"description": "Create secure webhook endpoints with signature verification and retry logic",
"version": "1.0.0"
},
"content": {
"files": [
{
"path": "README.md",
"sha256": "01991447744dea84981782d47f05ad82d51eccdeeba158c04f4b8a6f15da5086"
},
{
"path": ".claude-plugin/plugin.json",
"sha256": "ea2241a65c59cfc8d62fcfc0ce2d6989d6ce55f9f32219dc17027dd0c7614f95"
},
{
"path": "commands/create-webhook-handler.md",
"sha256": "3545b87b71616aefd4559c38946aac51f1a02c0a39d74102e272b21eab9d3dc8"
},
{
"path": "skills/skill-adapter/references/examples.md",
"sha256": "922bbc3c4ebf38b76f515b5c1998ebde6bf902233e00e2c5a0e9176f975a7572"
},
{
"path": "skills/skill-adapter/references/best-practices.md",
"sha256": "c8f32b3566252f50daacd346d7045a1060c718ef5cfb07c55a0f2dec5f1fb39e"
},
{
"path": "skills/skill-adapter/references/README.md",
"sha256": "efc5da262c37e37da6a7e2cc862e0c4e4beae3a01e770de1d4d982e3ab55638c"
},
{
"path": "skills/skill-adapter/scripts/helper-template.sh",
"sha256": "0881d5660a8a7045550d09ae0acc15642c24b70de6f08808120f47f86ccdf077"
},
{
"path": "skills/skill-adapter/scripts/validation.sh",
"sha256": "92551a29a7f512d2036e4f1fb46c2a3dc6bff0f7dde4a9f699533e446db48502"
},
{
"path": "skills/skill-adapter/scripts/README.md",
"sha256": "69f93625d3baf6c6febd597368201e63c24a2ea1b6973e46cfad7194406645c6"
},
{
"path": "skills/skill-adapter/assets/test-data.json",
"sha256": "ac17dca3d6e253a5f39f2a2f1b388e5146043756b05d9ce7ac53a0042eee139d"
},
{
"path": "skills/skill-adapter/assets/webhook_handler_template.py",
"sha256": "e1271855ed5cf351b9c2d4394ff1d1337eb3fdd28ce606508db773ff19517191"
},
{
"path": "skills/skill-adapter/assets/README.md",
"sha256": "2dfcb62af4c2a1873dd67e673d071a9a55d0640dc1404df57bec6d7f8961c271"
},
{
"path": "skills/skill-adapter/assets/example_event_schema.json",
"sha256": "0a9106035babcff2db5b63f3ef10d439ccdea92505ee47fcbbd36796507e3064"
},
{
"path": "skills/skill-adapter/assets/example_webhook_payload.json",
"sha256": "df8f93e7d0da91fb2482ad7e3a0b0ba727615bd86c9c7d7a80a6ce5d3b57615f"
},
{
"path": "skills/skill-adapter/assets/skill-schema.json",
"sha256": "f5639ba823a24c9ac4fb21444c0717b7aefde1a4993682897f5bf544f863c2cd"
},
{
"path": "skills/skill-adapter/assets/config-template.json",
"sha256": "0c2ba33d2d3c5ccb266c0848fc43caa68a2aa6a80ff315d4b378352711f83e1c"
}
],
"dirSha256": "9fe2ac6f8302ba61bc68427db8c4de6513a4ac949c5c55d8b48beb9b138712f6"
},
"security": {
"scannedAt": null,
"scannerVersion": null,
"flags": []
}
}

View File

@@ -0,0 +1,7 @@
# Assets
Bundled resources for webhook-handler-creator skill
- [ ] webhook_handler_template.py: A basic Python template for a webhook handler with signature verification and idempotency.
- [ ] example_webhook_payload.json: Example JSON payload for a webhook.
- [ ] example_event_schema.json: Example JSON schema for a webhook event.

View File

@@ -0,0 +1,32 @@
{
"skill": {
"name": "skill-name",
"version": "1.0.0",
"enabled": true,
"settings": {
"verbose": false,
"autoActivate": true,
"toolRestrictions": true
}
},
"triggers": {
"keywords": [
"example-trigger-1",
"example-trigger-2"
],
"patterns": []
},
"tools": {
"allowed": [
"Read",
"Grep",
"Bash"
],
"restricted": []
},
"metadata": {
"author": "Plugin Author",
"category": "general",
"tags": []
}
}

View File

@@ -0,0 +1,125 @@
{
"_comment": "Example JSON schema for a webhook event related to order processing.",
"type": "object",
"properties": {
"event_type": {
"type": "string",
"description": "Type of event. e.g., order.created, order.updated, order.cancelled",
"example": "order.created"
},
"event_id": {
"type": "string",
"format": "uuid",
"description": "Unique identifier for the event.",
"example": "a1b2c3d4-e5f6-7890-1234-567890abcdef"
},
"timestamp": {
"type": "string",
"format": "date-time",
"description": "Timestamp of when the event occurred (ISO 8601 format).",
"example": "2024-01-27T12:00:00Z"
},
"data": {
"type": "object",
"description": "The actual data associated with the event.",
"properties": {
"order_id": {
"type": "string",
"description": "Unique identifier for the order.",
"example": "ORD-2024-001"
},
"customer_id": {
"type": "string",
"description": "Unique identifier for the customer.",
"example": "CUST-001"
},
"amount": {
"type": "number",
"format": "float",
"description": "Total order amount.",
"example": 99.99
},
"currency": {
"type": "string",
"description": "Currency code (ISO 4217).",
"example": "USD"
},
"status": {
"type": "string",
"description": "Current status of the order.",
"example": "pending",
"enum": ["pending", "processing", "shipped", "delivered", "cancelled"]
},
"shipping_address": {
"type": "object",
"description": "Shipping address details.",
"properties": {
"street": {
"type": "string",
"example": "123 Main St"
},
"city": {
"type": "string",
"example": "Anytown"
},
"state": {
"type": "string",
"example": "CA"
},
"zip": {
"type": "string",
"example": "91234"
},
"country": {
"type": "string",
"example": "USA"
}
},
"required": ["street", "city", "state", "zip", "country"]
},
"items": {
"type": "array",
"description": "List of items in the order.",
"items": {
"type": "object",
"properties": {
"product_id": {
"type": "string",
"example": "PROD-001"
},
"quantity": {
"type": "integer",
"example": 2
},
"price": {
"type": "number",
"format": "float",
"example": 49.99
}
},
"required": ["product_id", "quantity", "price"]
}
}
},
"required": ["order_id", "customer_id", "amount", "currency", "status", "shipping_address", "items"]
},
"metadata": {
"type": "object",
"description": "Additional metadata about the event.",
"properties": {
"source": {
"type": "string",
"description": "Source of the event (e.g., 'payment_gateway', 'crm').",
"example": "payment_gateway"
},
"version": {
"type": "string",
"description": "Version of the event schema.",
"example": "1.0"
}
},
"required": ["source", "version"]
}
},
"required": ["event_type", "event_id", "timestamp", "data", "metadata"]
}

View File

@@ -0,0 +1,53 @@
{
"_comment": "Example JSON payload for a webhook. This payload represents a common event: an order being placed.",
"event_type": "order.created",
"_comment": "Type of event that triggered the webhook.",
"event_id": "evt_1234567890abcdef",
"_comment": "Unique identifier for this specific event. Used for idempotency.",
"timestamp": 1678886400,
"_comment": "Unix timestamp of when the event occurred.",
"data": {
"_comment": "The actual data associated with the event. Structure varies depending on event_type.",
"order_id": "ord_abcdef1234567890",
"customer_id": "cust_fedcba0987654321",
"order_total": 99.99,
"currency": "USD",
"items": [
{
"item_id": "item_11223344",
"quantity": 1,
"price": 49.99,
"name": "Awesome Widget"
},
{
"item_id": "item_55667788",
"quantity": 1,
"price": 50.00,
"name": "Deluxe Gadget"
}
],
"shipping_address": {
"name": "John Doe",
"address_line1": "123 Main St",
"address_line2": null,
"city": "Anytown",
"state": "CA",
"zip_code": "91234",
"country": "US"
},
"billing_address": {
"name": "John Doe",
"address_line1": "123 Main St",
"address_line2": null,
"city": "Anytown",
"state": "CA",
"zip_code": "91234",
"country": "US"
}
},
"metadata": {
"_comment": "Optional metadata associated with the event. Can be used to pass additional context.",
"source": "web",
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36"
}
}

View File

@@ -0,0 +1,28 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Claude Skill Configuration",
"type": "object",
"required": ["name", "description"],
"properties": {
"name": {
"type": "string",
"pattern": "^[a-z0-9-]+$",
"maxLength": 64,
"description": "Skill identifier (lowercase, hyphens only)"
},
"description": {
"type": "string",
"maxLength": 1024,
"description": "What the skill does and when to use it"
},
"allowed-tools": {
"type": "string",
"description": "Comma-separated list of allowed tools"
},
"version": {
"type": "string",
"pattern": "^\\d+\\.\\d+\\.\\d+$",
"description": "Semantic version (x.y.z)"
}
}
}

View File

@@ -0,0 +1,27 @@
{
"testCases": [
{
"name": "Basic activation test",
"input": "trigger phrase example",
"expected": {
"activated": true,
"toolsUsed": ["Read", "Grep"],
"success": true
}
},
{
"name": "Complex workflow test",
"input": "multi-step trigger example",
"expected": {
"activated": true,
"steps": 3,
"toolsUsed": ["Read", "Write", "Bash"],
"success": true
}
}
],
"fixtures": {
"sampleInput": "example data",
"expectedOutput": "processed result"
}
}

View File

@@ -0,0 +1,252 @@
#!/usr/bin/env python3
"""
A basic Python template for a webhook handler with signature verification
and idempotency. Provides a starting point for building robust and secure
webhook integrations.
"""
import hashlib
import hmac
import json
import logging
import os
import time
from functools import wraps
from http import HTTPStatus
from typing import Callable, Dict
from flask import Flask, request, jsonify
# Configure logging
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s')
# Environment variables (replace with your actual values)
WEBHOOK_SECRET = os.environ.get("WEBHOOK_SECRET", "your_secret_key")
MAX_RETRIES = int(os.environ.get("MAX_RETRIES", 3))
RETRY_DELAY = int(os.environ.get("RETRY_DELAY", 1)) # Initial delay in seconds
app = Flask(__name__)
class WebhookError(Exception):
"""Base class for webhook-related exceptions."""
pass
class SignatureVerificationError(WebhookError):
"""Raised when webhook signature verification fails."""
pass
class IdempotencyError(WebhookError):
"""Raised when idempotency check fails."""
pass
def verify_signature(request_data: bytes, signature: str, secret: str) -> None:
"""
Verifies the webhook signature against the request body and secret.
Args:
request_data: The raw bytes of the request body.
signature: The signature sent in the webhook request headers.
secret: The secret key used to generate the signature.
Raises:
SignatureVerificationError: If the signature does not match.
"""
try:
expected_signature = hmac.new(
secret.encode('utf-8'),
request_data,
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected_signature, signature):
raise SignatureVerificationError("Invalid webhook signature.")
except Exception as e:
logging.error(f"Signature verification failed: {e}")
raise SignatureVerificationError("Signature verification failed.") from e
def idempotent(func: Callable) -> Callable:
"""
Decorator to ensure idempotency of webhook requests. This example uses a
simple in-memory store. For production, use a persistent database
(e.g., Redis, PostgreSQL).
Args:
func: The function to decorate (webhook handler).
Returns:
The decorated function.
Raises:
IdempotencyError: If the request ID has already been processed.
"""
processed_requests: Dict[str, bool] = {} # In-memory store (replace in production)
@wraps(func)
def wrapper(*args, **kwargs):
request_id = request.headers.get("X-Request-ID")
if not request_id:
logging.warning("Missing X-Request-ID header. Idempotency check skipped.")
return func(*args, **kwargs) # Skip idempotency check if no request ID
if request_id in processed_requests:
raise IdempotencyError(f"Request with ID {request_id} already processed.")
try:
result = func(*args, **kwargs)
processed_requests[request_id] = True # Mark as processed
return result
except Exception as e:
logging.error(f"Error processing request: {e}")
raise
return wrapper
def retry(func: Callable, max_retries: int = MAX_RETRIES, delay: int = RETRY_DELAY) -> Callable:
"""
Decorator to add retry logic with exponential backoff.
Args:
func: The function to decorate.
max_retries: The maximum number of retries.
delay: The initial delay in seconds.
Returns:
The decorated function.
"""
@wraps(func)
def wrapper(*args, **kwargs):
attempts = 0
while attempts < max_retries:
try:
return func(*args, **kwargs)
except Exception as e:
attempts += 1
logging.warning(f"Attempt {attempts} failed: {e}. Retrying in {delay} seconds...")
time.sleep(delay)
delay *= 2 # Exponential backoff
logging.error(f"Max retries reached. Function {func.__name__} failed.")
raise
return wrapper
@app.route('/webhook', methods=['POST'])
@idempotent
@retry
def handle_webhook():
"""
Handles incoming webhook requests.
This function verifies the signature, processes the event, and returns a
success response. It also includes error handling and retry logic.
"""
signature = request.headers.get('X-Webhook-Signature')
if not signature:
logging.warning("Missing X-Webhook-Signature header.")
return jsonify({"error": "Missing signature"}), HTTPStatus.BAD_REQUEST
request_data = request.get_data()
try:
verify_signature(request_data, signature, WEBHOOK_SECRET)
except SignatureVerificationError as e:
logging.warning(f"Signature verification failed: {e}")
return jsonify({"error": str(e)}), HTTPStatus.UNAUTHORIZED
try:
payload = json.loads(request_data.decode('utf-8'))
event_type = payload.get("type") # Example: Get event type from payload
# Route the event to the appropriate handler (replace with your logic)
if event_type == "user.created":
process_user_created_event(payload)
elif event_type == "payment.succeeded":
process_payment_succeeded_event(payload)
else:
logging.warning(f"Unhandled event type: {event_type}")
return jsonify({"status": "unhandled"}), HTTPStatus.OK # Acknowledge the event
return jsonify({"status": "success"}), HTTPStatus.OK
except json.JSONDecodeError:
logging.error("Invalid JSON payload")
return jsonify({"error": "Invalid JSON payload"}), HTTPStatus.BAD_REQUEST
except Exception as e:
logging.exception("Error processing webhook")
return jsonify({"error": "Internal server error"}), HTTPStatus.INTERNAL_SERVER_ERROR
def process_user_created_event(payload: Dict) -> None:
"""
Processes a user.created event. This is a placeholder; replace with
your actual business logic.
Args:
payload: The event payload as a dictionary.
"""
user_id = payload.get("user_id")
logging.info(f"Processing user.created event for user ID: {user_id}")
# Add your business logic here (e.g., create user in your system)
time.sleep(0.1) # Simulate some processing time
def process_payment_succeeded_event(payload: Dict) -> None:
"""
Processes a payment.succeeded event. This is a placeholder; replace with
your actual business logic.
Args:
payload: The event payload as a dictionary.
"""
payment_id = payload.get("payment_id")
logging.info(f"Processing payment.succeeded event for payment ID: {payment_id}")
# Add your business logic here (e.g., update order status)
time.sleep(0.2) # Simulate some processing time
@app.errorhandler(SignatureVerificationError)
def handle_signature_error(error):
"""Handles SignatureVerificationError exceptions."""
return jsonify({"error": str(error)}), HTTPStatus.UNAUTHORIZED
@app.errorhandler(IdempotencyError)
def handle_idempotency_error(error):
"""Handles IdempotencyError exceptions."""
return jsonify({"error": str(error)}), HTTPStatus.CONFLICT
@app.errorhandler(Exception)
def handle_generic_error(error):
"""Handles generic exceptions."""
logging.exception("Unhandled exception")
return jsonify({"error": "Internal server error"}), HTTPStatus.INTERNAL_SERVER_ERROR
if __name__ == '__main__':
# Example Usage:
#
# 1. Set the WEBHOOK_SECRET environment variable.
# 2. Run the Flask app: python webhook_handler_template.py
# 3. Send a POST request to /webhook with a valid X-Webhook-Signature header.
#
# Example request:
#
# POST /webhook HTTP/1.1
# X-Webhook-Signature: <calculated_signature>
# X-Request-ID: <unique_request_id>
# Content-Type: application/json
#
# {
# "type": "user.created",
# "user_id": "123"
# }
app.run(debug=True, host='0.0.0.0', port=5000)

View File

@@ -0,0 +1,10 @@
# References
Bundled resources for webhook-handler-creator skill
- [ ] webhook_security_best_practices.md: Detailed guide on securing webhooks, including signature verification, rate limiting, and input validation.
- [ ] idempotency_implementation.md: Explanation of idempotency and how to implement it in webhook handlers.
- [ ] retry_logic_implementation.md: Guide on implementing retry logic with exponential backoff for webhook handlers.
- [ ] hmac_signature_verification.md: In-depth explanation of HMAC signature verification.
- [ ] event_routing_strategies.md: Different strategies for routing webhook events to appropriate handlers.
- [ ] dead_letter_queue_implementation.md: Guide on setting up a dead letter queue for failed webhook events.

View File

@@ -0,0 +1,69 @@
# Skill Best Practices
Guidelines for optimal skill usage and development.
## For Users
### Activation Best Practices
1. **Use Clear Trigger Phrases**
- Match phrases from skill description
- Be specific about intent
- Provide necessary context
2. **Provide Sufficient Context**
- Include relevant file paths
- Specify scope of analysis
- Mention any constraints
3. **Understand Tool Permissions**
- Check allowed-tools in frontmatter
- Know what the skill can/cannot do
- Request appropriate actions
### Workflow Optimization
- Start with simple requests
- Build up to complex workflows
- Verify each step before proceeding
- Use skill consistently for related tasks
## For Developers
### Skill Development Guidelines
1. **Clear Descriptions**
- Include explicit trigger phrases
- Document all capabilities
- Specify limitations
2. **Proper Tool Permissions**
- Use minimal necessary tools
- Document security implications
- Test with restricted tools
3. **Comprehensive Documentation**
- Provide usage examples
- Document common pitfalls
- Include troubleshooting guide
### Maintenance
- Keep version updated
- Test after tool updates
- Monitor user feedback
- Iterate on descriptions
## Performance Tips
- Scope skills to specific domains
- Avoid overlapping trigger phrases
- Keep descriptions under 1024 chars
- Test activation reliability
## Security Considerations
- Never include secrets in skill files
- Validate all inputs
- Use read-only tools when possible
- Document security requirements

View File

@@ -0,0 +1,70 @@
# Skill Usage Examples
This document provides practical examples of how to use this skill effectively.
## Basic Usage
### Example 1: Simple Activation
**User Request:**
```
[Describe trigger phrase here]
```
**Skill Response:**
1. Analyzes the request
2. Performs the required action
3. Returns results
### Example 2: Complex Workflow
**User Request:**
```
[Describe complex scenario]
```
**Workflow:**
1. Step 1: Initial analysis
2. Step 2: Data processing
3. Step 3: Result generation
4. Step 4: Validation
## Advanced Patterns
### Pattern 1: Chaining Operations
Combine this skill with other tools:
```
Step 1: Use this skill for [purpose]
Step 2: Chain with [other tool]
Step 3: Finalize with [action]
```
### Pattern 2: Error Handling
If issues occur:
- Check trigger phrase matches
- Verify context is available
- Review allowed-tools permissions
## Tips & Best Practices
- ✅ Be specific with trigger phrases
- ✅ Provide necessary context
- ✅ Check tool permissions match needs
- ❌ Avoid vague requests
- ❌ Don't mix unrelated tasks
## Common Issues
**Issue:** Skill doesn't activate
**Solution:** Use exact trigger phrases from description
**Issue:** Unexpected results
**Solution:** Check input format and context
## See Also
- Main SKILL.md for full documentation
- scripts/ for automation helpers
- assets/ for configuration examples

View File

@@ -0,0 +1,7 @@
# Scripts
Bundled resources for webhook-handler-creator skill
- [ ] create_webhook_handler.py: Generates a basic webhook handler with signature verification and idempotency logic.
- [ ] test_webhook_handler.py: Creates test cases for the generated webhook handler.
- [ ] deploy_webhook_handler.sh: Deploys the webhook handler to a server (e.g., using Flask or FastAPI).

View File

@@ -0,0 +1,42 @@
#!/bin/bash
# Helper script template for skill automation
# Customize this for your skill's specific needs
set -e
function show_usage() {
echo "Usage: $0 [options]"
echo ""
echo "Options:"
echo " -h, --help Show this help message"
echo " -v, --verbose Enable verbose output"
echo ""
}
# Parse arguments
VERBOSE=false
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_usage
exit 0
;;
-v|--verbose)
VERBOSE=true
shift
;;
*)
echo "Unknown option: $1"
show_usage
exit 1
;;
esac
done
# Your skill logic here
if [ "$VERBOSE" = true ]; then
echo "Running skill automation..."
fi
echo "✅ Complete"

View File

@@ -0,0 +1,32 @@
#!/bin/bash
# Skill validation helper
# Validates skill activation and functionality
set -e
echo "🔍 Validating skill..."
# Check if SKILL.md exists
if [ ! -f "../SKILL.md" ]; then
echo "❌ Error: SKILL.md not found"
exit 1
fi
# Validate frontmatter
if ! grep -q "^---$" "../SKILL.md"; then
echo "❌ Error: No frontmatter found"
exit 1
fi
# Check required fields
if ! grep -q "^name:" "../SKILL.md"; then
echo "❌ Error: Missing 'name' field"
exit 1
fi
if ! grep -q "^description:" "../SKILL.md"; then
echo "❌ Error: Missing 'description' field"
exit 1
fi
echo "✅ Skill validation passed"