Initial commit
This commit is contained in:
15
.claude-plugin/plugin.json
Normal file
15
.claude-plugin/plugin.json
Normal 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
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# webhook-handler-creator
|
||||
|
||||
Create secure webhook endpoints with signature verification and retry logic
|
||||
1104
commands/create-webhook-handler.md
Normal file
1104
commands/create-webhook-handler.md
Normal file
File diff suppressed because it is too large
Load Diff
97
plugin.lock.json
Normal file
97
plugin.lock.json
Normal 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": []
|
||||
}
|
||||
}
|
||||
7
skills/skill-adapter/assets/README.md
Normal file
7
skills/skill-adapter/assets/README.md
Normal 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.
|
||||
32
skills/skill-adapter/assets/config-template.json
Normal file
32
skills/skill-adapter/assets/config-template.json
Normal 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": []
|
||||
}
|
||||
}
|
||||
125
skills/skill-adapter/assets/example_event_schema.json
Normal file
125
skills/skill-adapter/assets/example_event_schema.json
Normal 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"]
|
||||
}
|
||||
53
skills/skill-adapter/assets/example_webhook_payload.json
Normal file
53
skills/skill-adapter/assets/example_webhook_payload.json
Normal 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"
|
||||
}
|
||||
}
|
||||
28
skills/skill-adapter/assets/skill-schema.json
Normal file
28
skills/skill-adapter/assets/skill-schema.json
Normal 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)"
|
||||
}
|
||||
}
|
||||
}
|
||||
27
skills/skill-adapter/assets/test-data.json
Normal file
27
skills/skill-adapter/assets/test-data.json
Normal 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"
|
||||
}
|
||||
}
|
||||
252
skills/skill-adapter/assets/webhook_handler_template.py
Normal file
252
skills/skill-adapter/assets/webhook_handler_template.py
Normal 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)
|
||||
10
skills/skill-adapter/references/README.md
Normal file
10
skills/skill-adapter/references/README.md
Normal 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.
|
||||
69
skills/skill-adapter/references/best-practices.md
Normal file
69
skills/skill-adapter/references/best-practices.md
Normal 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
|
||||
70
skills/skill-adapter/references/examples.md
Normal file
70
skills/skill-adapter/references/examples.md
Normal 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
|
||||
7
skills/skill-adapter/scripts/README.md
Normal file
7
skills/skill-adapter/scripts/README.md
Normal 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).
|
||||
42
skills/skill-adapter/scripts/helper-template.sh
Executable file
42
skills/skill-adapter/scripts/helper-template.sh
Executable 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"
|
||||
32
skills/skill-adapter/scripts/validation.sh
Executable file
32
skills/skill-adapter/scripts/validation.sh
Executable 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"
|
||||
Reference in New Issue
Block a user