webhook-orchestrator
Design webhook event routing, payload validation, and retry logic for real-time event-driven integrations where source systems push policy changes, loan status updates, or payment events.
What this skill does
# Webhook Orchestrator
Produce a complete webhook receiver and event routing specification. The output is the technical design a developer uses to build the webhook endpoint, event router, and retry infrastructure.
## Webhook Receiver Design
**Endpoint specification**:
| Field | Value |
|-------|-------|
| URL structure | `https://integrations.[firm].com/webhooks/{source-system}` |
| HTTP method | POST only (reject GET, PUT, DELETE with 405) |
| Content-Type required | `application/json` |
| TLS | Required — TLS 1.2 minimum. Reject HTTP (respond with 301 or close connection). |
| IP allowlist | If source system provides a list of static egress IPs, add them to the allowlist. Document the IP list and who to contact at the vendor when IPs change. |
| Response SLA | Respond with HTTP 200 within 5 seconds. Any processing beyond 5 seconds must be async (acknowledge immediately, process in background queue). |
| Endpoint authentication | Shared secret signature (HMAC) OR basic auth token in header — see Payload Validation section |
**Why respond immediately**: Webhook senders typically time out after 5-30 seconds and may retry if no response is received. All processing logic (database writes, downstream API calls, notifications) must happen asynchronously in a queue after the 200 response is sent.
**Response structure**:
```json
HTTP 200 OK
{
"received": true,
"event_id": "{deduplication-key-extracted-from-payload}",
"queued_at": "2026-04-15T14:30:00Z"
}
HTTP 400 Bad Request (invalid payload structure):
{
"error": "INVALID_PAYLOAD",
"message": "Required field 'event_type' is missing"
}
HTTP 401 Unauthorized (signature validation failed):
{
"error": "INVALID_SIGNATURE",
"message": "Webhook signature does not match"
}
```
## Payload Validation
Validate every incoming webhook before processing. Reject invalid payloads at the receiver — do not pass them to the event queue.
### HMAC Signature Verification
Most modern webhook senders include a signature in the request headers. Verify it before processing:
```typescript
function validateHmacSignature(
payload: string, // raw request body as string — do NOT parse JSON first
receivedSignature: string, // from header: X-Hub-Signature-256 or similar
secret: string // shared secret from Key Vault
): boolean {
// Vendor format may be: sha256={hex-signature}
const expectedSignature = 'sha256=' +
crypto.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex');
// Use timing-safe comparison to prevent timing attacks
return crypto.timingSafeEqual(
Buffer.from(receivedSignature),
Buffer.from(expectedSignature)
);
}
// In the request handler:
const rawBody = req.body.toString('utf8'); // read as raw string before JSON parse
const signature = req.headers['x-webhook-signature'] as string;
if (!validateHmacSignature(rawBody, signature, webhookSecret)) {
return res.status(401).json({ error: 'INVALID_SIGNATURE' });
}
const payload = JSON.parse(rawBody);
```
**Critical**: Read the raw body before parsing. JSON parsers may reorder fields, changing the string and breaking the HMAC comparison.
**Header name by vendor**: Each vendor uses a different header name. Document the exact header name:
| System | Signature Header | Format |
|--------|-----------------|--------|
| Generic / custom | X-Webhook-Signature | sha256={hex} |
| GitHub-style | X-Hub-Signature-256 | sha256={hex} |
| Stripe-style | Stripe-Signature | t={timestamp},v1={signature} |
| Custom vendor | X-[System]-Signature | {hex} |
For Stripe-style signatures (timestamp + signature), also validate that the timestamp is within 5 minutes of current time to prevent replay attacks.
### Source IP Validation
If the webhook source provides a static egress IP list:
```typescript
const ALLOWED_IPS = ['203.0.113.10', '203.0.113.11']; // document source
function validateSourceIP(requestIP: string): boolean {
return ALLOWED_IPS.includes(requestIP);
}
```
Document where the IP list comes from and how to update it when the vendor changes their egress IPs.
### Payload Schema Validation
After signature verification, validate the payload structure:
```typescript
// Required fields for all events from this source
const requiredFields = ['event_type', 'event_id', 'timestamp', 'data'];
for (const field of requiredFields) {
if (!(field in payload)) {
return res.status(400).json({
error: 'INVALID_PAYLOAD',
message: `Required field '${field}' is missing`
});
}
}
// Validate event_type is a known type
const knownEventTypes = new Set([
'policy.created', 'policy.updated', 'policy.cancelled',
'claim.submitted', 'claim.status_changed',
'payment.received', 'payment.failed'
]);
if (!knownEventTypes.has(payload.event_type)) {
// Log unknown event type — do not reject (forward-compatibility)
logger.warn('Unknown event type received', { eventType: payload.event_type });
// Still return 200 — do not cause the sender to retry unknown future events
return res.status(200).json({ received: true, note: 'event_type not handled' });
}
```
## Event Routing
Extract the event type and route to the appropriate handler. Use a registry pattern — avoid a giant switch statement.
**Event routing table**:
| Event Type | Handler Module | Downstream System | Priority |
|-----------|---------------|------------------|----------|
| `policy.created` | handlers/policy-created.ts | CRM, SharePoint, Teams notification | High |
| `policy.updated` | handlers/policy-updated.ts | CRM, SharePoint | Normal |
| `policy.cancelled` | handlers/policy-cancelled.ts | CRM, Teams alert, Renewal tracker | High |
| `claim.submitted` | handlers/claim-submitted.ts | Claims SharePoint library, Teams alert | High |
| `claim.status_changed` | handlers/claim-status.ts | Claims tracker, agent notification | Normal |
| `payment.received` | handlers/payment-received.ts | AMS, accounting system | High |
| `payment.failed` | handlers/payment-failed.ts | Agent alert, client outreach queue | High |
**Routing implementation pattern**:
```typescript
const eventHandlers: Record<string, EventHandler> = {
'policy.created': policyCreatedHandler,
'policy.updated': policyUpdatedHandler,
'policy.cancelled': policyCancelledHandler,
'claim.submitted': claimSubmittedHandler,
'claim.status_changed': claimStatusHandler,
'payment.received': paymentReceivedHandler,
'payment.failed': paymentFailedHandler,
};
// In webhook receiver (after validation):
await eventQueue.enqueue({
eventType: payload.event_type,
eventId: payload.event_id,
timestamp: payload.timestamp,
data: payload.data,
receivedAt: new Date().toISOString(),
source: 'ams-webhook'
});
// In queue consumer:
const handler = eventHandlers[event.eventType];
if (handler) {
await handler.process(event);
} else {
logger.warn('No handler registered', { eventType: event.eventType });
}
```
**Fanout**: For events that trigger multiple downstream actions, the handler orchestrates all actions. Each action is independent — if one fails, the others should still proceed (use Promise.allSettled, not Promise.all).
## Idempotency Design
The source system may send the same event multiple times (network retry, system restart). The receiver must be idempotent — processing the same event twice must produce the same result as processing it once.
**Deduplication key extraction**:
- Use `event_id` from the payload as the deduplication key. If absent, compute: `sha256(event_type + JSON.stringify(data) + timestamp)`.
**Idempotency store** (choose based on scale):
- Low volume (< 1000 events/day): SharePoint list with event_id column, indexed
- Medium volume: Redis cache with TTL
- High volume: Azure Table Storage
**Deduplication check**:
```typescript
async function isDuplicate(eventId: string): Promise<boolean> {
// Check idempotency store (Redis example)
const exists = await redis.exists(`webhook:processed:${eventId}`);
retRelated in Design
contribute
IncludedLocal-only OSS contribution command center. Auto-refreshes the user's in-flight PR and issue state on invoke so conversations start with full context — no need to brief Claude on what's in flight. Helps the user find issues to contribute to on GitHub, builds per-repo dossiers of what each upstream expects (CLA, DCO, branch convention, AI policy, draft-first, review bots, issue templates), runs deterministic gates before any external action so AI-assisted contributions don't reach maintainers as slop. State is markdown-only: candidate files at ~/.contribute-system/candidates/, repo dossiers at ~/.contribute-system/research/, append-only event log at ~/.contribute-system/log.jsonl. No database, no cloud calls. Use when the user asks about their PRs / issues / contributions, wants to find new work to take on, claim an issue, build/refresh a repo's dossier, or draft a Design Issue or PR. Trigger with "/contribute", "what's my PR status", "find a contribution", "claim issue X", "draft a Design Issue for Y", "refresh dossier for Z".
architectural-analysis
IncludedUser-triggered deep architectural analysis of a codebase or scoped subtree across eight modes — information architecture, data flow, integration points, UI surfaces, interaction patterns, data model, control flow, and failure modes. This skill should be used when the user asks to "diagram this codebase," "map the architecture," "show the data flow," "give me an ERD," "trace control flow," "find the integration points," "verify the layout pattern," "audit the UX architecture," or any similar request whose primary deliverable is mermaid diagrams plus cited reports under docs/architecture/. Dispatches haiku/sonnet sub-agents in parallel for per-mode exploration, then verifies every citation mechanically before any node lands in a diagram. Not for one-off prose explanations of code (use code-explanation) or for high-level system design from scratch (use system-design).
mcp
IncludedModel Context Protocol (MCP) server development and tool management. Languages: Python, TypeScript. Capabilities: build MCP servers, integrate external APIs, discover/execute MCP tools, manage multi-server configs, design agent-centric tools. Actions: create, build, integrate, discover, execute, configure MCP servers/tools. Keywords: MCP, Model Context Protocol, MCP server, MCP tool, stdio transport, SSE transport, tool discovery, resource provider, prompt template, external API integration, Gemini CLI MCP, Claude MCP, agent tools, tool execution, server config. Use when: building MCP servers, integrating external APIs as MCP tools, discovering available MCP tools, executing MCP capabilities, configuring multi-server setups, designing tools for AI agents.
react-native-skia
IncludedDesign, build, debug, and optimise high-polish animated graphics in React Native or Expo using @shopify/react-native-skia, Reanimated, and Gesture Handler. Use when the user wants canvas-driven UI, shaders, paths, rich text, image filters, sprite fields, Skottie, video frames, snapshots, web CanvasKit setup, or performance tuning for custom motion-heavy elements such as loaders, hero art, cards, charts, progress indicators, particle systems, or gesture-driven surfaces. Also use when the user asks for fluid, glow, glass, blob, parallax, 60fps/120fps, or GPU-friendly animated effects in React Native, even if they do not explicitly say "Skia". Do not use for ordinary form/layout work with standard views.
plaid
IncludedProduct Led AI Development — guides founders from idea to launched product. Six capabilities: Idea (discover a product idea), Validate (pressure-test the idea against fatal flaws, problem reality, competition, and 2-week MVP feasibility), Plan (vision intake + document generation), Design (translate image references into a design.md spec), Launch (go-to-market strategy), and Build (roadmap execution). Use when someone says "PLAID", "plaid idea", "help me find an idea", "product idea", "idea from my business", "idea from my expertise", "plaid validate", "validate my idea", "pressure-test", "is this idea good", "find fatal flaws", "validate the problem", "plan a product", "define my vision", "generate a PRD", "product strategy", "plaid design", "design from image", "translate image to design", "create design.md", "extract design tokens", "plaid launch", "go-to-market", "launch plan", "GTM strategy", "launch playbook", "plaid build", "build the app", "start building", or "execute the roadmap".
nextjs-framer-motion-animations
IncludedAdds production-safe Motion for React or Framer Motion animations to Next.js apps, including reveal, hover and tap micro-interactions, whileInView, stagger, AnimatePresence, layout and layoutId transitions, reorder, scroll-linked UI, and lightweight route-content transitions. Use when the user asks to add, refactor, or debug Motion or Framer Motion in App Router or Pages Router codebases, especially around server/client boundaries, reduced motion, LazyMotion, bundle size, hydration, or route transitions. Avoid for GSAP-style timelines, WebGL or 3D scenes, heavy scroll storytelling, or CSS-only effects unless Motion is explicitly requested.