Novu Design Workflow
novuhq/novu
Design notification workflows the Novu way — choose channels, set severity, decide when a workflow is critical, configure digests, and route based on subscriber state.
A skill your agent uses when designing a webhook delivery system, implementing HMAC signature verification on a receiver, handling retries and failures on the sender side, building idempotent…
$ npx skills add kid-sid/claude-spellbook --skill webhooks -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install kid-sid/claude-spellbook webhooks --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/kid-sid/claude-spellbook.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/webhooks .claude/skills/webhooks && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "webhooks" agent skill from https://github.com/kid-sid/claude-spellbook/tree/main/skills/webhooks into .claude/skills/webhooks/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "webhooks", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/kid-sid/claude-spellbook/tree/main/skills/webhooksType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add kid-sid/claude-spellbook --skill webhooks -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install kid-sid/claude-spellbook webhooks --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/kid-sid/claude-spellbook.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/webhooks .agents/skills/webhooks && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "webhooks" agent skill from https://github.com/kid-sid/claude-spellbook/tree/main/skills/webhooks into .agents/skills/webhooks/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "webhooks", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add kid-sid/claude-spellbook --skill webhooks -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install kid-sid/claude-spellbook webhooks --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/kid-sid/claude-spellbook.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/webhooks .cursor/skills/webhooks && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "webhooks" agent skill from https://github.com/kid-sid/claude-spellbook/tree/main/skills/webhooks into .cursor/skills/webhooks/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "webhooks", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/kid-sid/claude-spellbook.git --path skills/webhooks--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add kid-sid/claude-spellbook --skill webhooks -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install kid-sid/claude-spellbook webhooks --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/kid-sid/claude-spellbook.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/webhooks .gemini/skills/webhooks && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "webhooks" agent skill from https://github.com/kid-sid/claude-spellbook/tree/main/skills/webhooks into .gemini/skills/webhooks/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "webhooks", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install kid-sid/claude-spellbook webhooksInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add kid-sid/claude-spellbook --skill webhooks -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/kid-sid/claude-spellbook.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/webhooks .github/skills/webhooks && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "webhooks" agent skill from https://github.com/kid-sid/claude-spellbook/tree/main/skills/webhooks into .github/skills/webhooks/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "webhooks", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add kid-sid/claude-spellbook --skill webhooks -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install kid-sid/claude-spellbook webhooks --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/kid-sid/claude-spellbook.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/webhooks .opencode/skills/webhooks && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "webhooks" agent skill from https://github.com/kid-sid/claude-spellbook/tree/main/skills/webhooks into .opencode/skills/webhooks/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "webhooks", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
webhooksA skill your agent uses when designing a webhook delivery system, implementing HMAC signature verification on a receiver, handling retries and failures on the sender side, building idempotent…
Webhooks is an agent skill from kid-sid/claude-spellbook. Use when designing a webhook delivery system, implementing HMAC signature verification on a receiver, handling retries and failures on the sender side, building idempotent webhook consumers, or testing webhook integrations locally.
Its SKILL.md is about 3.2k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Backend & APIs, covering Webhooks. The repository describes itself as: A curated collection of skills, prompts, and workflows that extend Claude's capabilities — your personal grimoire for AI-powered development. The licence is MIT.
Read from SKILL.md and the folder at commit a7c2ac9. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
stripenpxngrokcurlFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md. Its commands use npx and curl, which can reach the network depending on how they are called.
From URLs in SKILL.md, links to its own repository left out.
Names these keys or tokens, usually read from environment variables:
STRIPE_SECRETFrom names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Webhooks loads about 3.2k tokens when it runs. Until then it costs about 60 tokens; SKILL.md has 648 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from kid-sid/claude-spellbook at commit a7c2ac9, republished under its MIT licence (© kid-sid). 648 words, ~3,211 tokens.
.claude/skills/webhooks/SKILL.md (or your agent's skills folder).Patterns for reliable webhook delivery, signature verification, idempotent consumption, and local testing.
| Concern | Sender (you push) | Receiver (you consume) |
|---|---|---|
| Signing | Sign every payload with HMAC | Verify signature before processing |
| Delivery | Retry with backoff on non-2xx | Return 2xx immediately, process async |
| Ordering | Add sequence or timestamp | Don't assume ordered delivery |
| Idempotency | Include a stable event ID | Deduplicate on event ID |
| Schema | Version the event type | Handle unknown fields gracefully |
Every webhook payload should include a consistent envelope regardless of event type.
{
"id": "evt_01HX4K9MZQR8FVNJ2Y3BCD5",
"type": "order.completed",
"version": "2024-01",
"created_at": "2024-01-15T10:30:00Z",
"data": {
"order_id": "ord_789",
"amount": 4999,
"currency": "usd"
}
}| Field | Purpose |
|---|---|
id | Stable, unique event ID — used for deduplication |
type | Namespaced event name (resource.action) |
version | Schema version — allows evolving payloads without breaking receivers |
created_at | When the event occurred (not when it was delivered) |
data | Event-specific payload |
Never trust a webhook payload without verifying its signature. Use a constant-time comparison to prevent timing attacks.
# Python
import hmac, hashlib, json, time
def sign_payload(payload: dict, secret: str) -> tuple[str, str]:
body = json.dumps(payload, separators=(",", ":")).encode()
timestamp = str(int(time.time()))
signed = f"{timestamp}.{body.decode()}"
sig = hmac.new(secret.encode(), signed.encode(), hashlib.sha256).hexdigest()
return f"t={timestamp},v1={sig}", body// TypeScript
import { createHmac } from "crypto";
function signPayload(payload: object, secret: string): { signature: string; body: string } {
const body = JSON.stringify(payload);
const timestamp = Math.floor(Date.now() / 1000).toString();
const signed = `${timestamp}.${body}`;
const sig = createHmac("sha256", secret).update(signed).digest("hex");
return { signature: `t=${timestamp},v1=${sig}`, body };
}// Go
func SignPayload(payload []byte, secret string, now time.Time) string {
timestamp := strconv.FormatInt(now.Unix(), 10)
signed := timestamp + "." + string(payload)
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(signed))
sig := hex.EncodeToString(mac.Sum(nil))
return fmt.Sprintf("t=%s,v1=%s", timestamp, sig)
}# Python — FastAPI
import hmac, hashlib, time
from fastapi import Request, HTTPException
TOLERANCE_SECONDS = 300 # reject replays older than 5 minutes
async def verify_webhook(request: Request, secret: str) -> bytes:
sig_header = request.headers.get("X-Webhook-Signature", "")
body = await request.body()
parts = dict(p.split("=", 1) for p in sig_header.split(","))
timestamp = parts.get("t", "")
received_sig = parts.get("v1", "")
# Reject replayed requests
if abs(time.time() - int(timestamp)) > TOLERANCE_SECONDS:
raise HTTPException(400, "Webhook timestamp too old")
expected = hmac.new(
secret.encode(),
f"{timestamp}.{body.decode()}".encode(),
hashlib.sha256,
).hexdigest()
# Constant-time comparison — prevents timing attacks
if not hmac.compare_digest(expected, received_sig):
raise HTTPException(400, "Invalid webhook signature")
return body// TypeScript — Express
import { createHmac, timingSafeEqual } from "crypto";
import { Request, Response, NextFunction } from "express";
const TOLERANCE_SECONDS = 300;
export function verifyWebhook(secret: string) {
return (req: Request, res: Response, next: NextFunction) => {
const header = req.headers["x-webhook-signature"] as string ?? "";
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const { t: timestamp, v1: receivedSig } = parts;
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > TOLERANCE_SECONDS) {
return res.status(400).json({ error: "Webhook timestamp too old" });
}
const body = (req as any).rawBody as Buffer; // requires rawBody middleware
const expected = createHmac("sha256", secret)
.update(`${timestamp}.${body}`)
.digest("hex");
if (!timingSafeEqual(Buffer.from(expected), Buffer.from(receivedSig))) {
return res.status(400).json({ error: "Invalid webhook signature" });
}
next();
};
}// Go
func VerifyWebhook(r *http.Request, body []byte, secret string) error {
header := r.Header.Get("X-Webhook-Signature")
parts := parseHeader(header) // split "t=...,v1=..." into map
timestamp, received := parts["t"], parts["v1"]
ts, _ := strconv.ParseInt(timestamp, 10, 64)
if math.Abs(float64(time.Now().Unix()-ts)) > 300 {
return errors.New("webhook timestamp too old")
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(timestamp + "." + string(body)))
expected := hex.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(expected), []byte(received)) {
return errors.New("invalid webhook signature")
}
return nil
}Event occurs → persist to outbox → worker picks up → HTTP POST → record result
↓
success: mark delivered
failure: schedule retryUse the transactional outbox pattern — write the event to a DB table in the same transaction as the business change. A background worker reads and delivers it. This prevents events being lost if the app crashes between the DB write and the HTTP call.
# Python — outbox record
@dataclass
class WebhookDelivery:
id: str
endpoint_url: str
payload: dict
attempt: int = 0
max_attempts: int = 5
next_attempt_at: datetime = field(default_factory=datetime.utcnow)
delivered_at: datetime | None = None
last_error: str | None = None| Attempt | Delay |
|---|---|
| 1st retry | 30 seconds |
| 2nd retry | 5 minutes |
| 3rd retry | 30 minutes |
| 4th retry | 2 hours |
| 5th retry | 8 hours |
| After max | Move to dead-letter queue |
def next_retry_delay(attempt: int) -> int:
schedule = [30, 300, 1800, 7200, 28800]
return schedule[min(attempt, len(schedule) - 1)]import httpx
async def deliver_webhook(delivery: WebhookDelivery, secret: str) -> None:
sig, body = sign_payload(delivery.payload, secret)
try:
async with httpx.AsyncClient(timeout=10) as client:
r = await client.post(
delivery.endpoint_url,
content=body,
headers={
"Content-Type": "application/json",
"X-Webhook-Signature": sig,
"X-Webhook-ID": delivery.id,
},
)
if r.status_code < 200 or r.status_code >= 300:
raise ValueError(f"non-2xx: {r.status_code}")
await mark_delivered(delivery.id)
except Exception as exc:
await schedule_retry(delivery, error=str(exc))Webhooks can be delivered more than once. Always deduplicate on event.id.
# Python — Redis deduplication
import redis.asyncio as redis
async def handle_webhook(event: dict, r: redis.Redis) -> None:
event_id = event["id"]
key = f"webhook:seen:{event_id}"
# SET NX — only set if not already present
already_seen = not await r.set(key, "1", nx=True, ex=86400)
if already_seen:
return # duplicate — safe to ignore
# Process after deduplication
await process_event(event)// TypeScript — DB-based deduplication
async function handleWebhook(event: WebhookEvent): Promise<void> {
const inserted = await db.webhookEvent.upsert({
where: { id: event.id },
create: { id: event.id, type: event.type, processedAt: null },
update: {}, // no-op if already exists
});
if (inserted.processedAt) return; // already processed
await processEvent(event);
await db.webhookEvent.update({ where: { id: event.id }, data: { processedAt: new Date() } });
}| Scenario | Response | Notes |
|---|---|---|
| Accepted for processing | 200 OK | Body ignored by sender |
| Duplicate (already processed) | 200 OK | Not an error — idempotent |
| Invalid signature | 400 Bad Request | Do not retry |
| Unknown event type | 200 OK | Accept and ignore unknown types |
| Downstream not ready | 503 Service Unavailable | Sender will retry |
Always return 2xx immediately. Do the actual work asynchronously (queue it). A slow receiver causes the sender to timeout and retry unnecessarily.
# FastAPI — accept immediately, process async
@router.post("/webhooks/stripe")
async def stripe_webhook(request: Request, background_tasks: BackgroundTasks):
body = await verify_webhook(request, secret=STRIPE_SECRET)
event = json.loads(body)
background_tasks.add_task(process_stripe_event, event)
return {"received": True} # 200 immediately# Use a tunnel to expose localhost
npx localtunnel --port 3000 --subdomain my-app
# or
ngrok http 3000
# Replay a real webhook for testing
curl -X POST http://localhost:3000/webhooks \
-H "Content-Type: application/json" \
-H "X-Webhook-Signature: t=1234567890,v1=abc..." \
-d '{"id":"evt_test","type":"order.completed","data":{}}'
# Stripe CLI — replay events against local server
stripe listen --forward-to localhost:3000/webhooks/stripe
stripe trigger payment_intent.succeededhmac.compare_digest / timingSafeEqual / hmac.Equal; plain == leaks timing information.event.id via Redis or DB upsert© kid-sid, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in skills/webhooks of kid-sid/claude-spellbook.
Open the folder on GitHubat commit a7c2ac9
Webhooks next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Webhooks this skillkid-sid/claude-spellbook | 189 | — | ~3.2k | Automated safety check: Pass | MIT | |
| Novu Design Workflownovuhq/novu | 40k | — | ~2.6k | Automated safety check: Pass | Custom licence | |
| Golivemikehasa/golive-skill | 1.2k | — | ~13k | Automated safety check: Notes | MIT | |
| Stripe Appsfossasia/eventyay | 1.7k | 1 repos | ~3.6k | Automated safety check: Pass | Apache-2.0 | |
| Dingtalk Messageagentscope-ai/ReMe | 3.6k | — | ~1.6k | Automated safety check: Pass | Apache-2.0 | |
| PR Review Provideryansongda/pay | 5.4k | — | ~2.4k | Automated safety check: Pass | MIT |
novuhq/novu
Design notification workflows the Novu way — choose channels, set severity, decide when a workflow is critical, configure digests, and route based on subscriber state.
mikehasa/golive-skill
Take an agent-written app from repo to live production on the user's OWN accounts, with providers they choose (hosting, database, auth, payments, email, domain/DNS).
fossasia/eventyay
A skill your agent uses when building, modifying, or reviewing a Stripe App — or when the user describes something that implies one (e.g.
agentscope-ai/ReMe
钉钉消息发送技能。支持企业内部机器人(批量单聊/群聊)和 Webhook 自定义机器人两种接入方式,支持多机器人管理,支持文本、Markdown、链接、ActionCard、FeedCard等多种消息类型。
yansongda/pay
A skill your agent uses when reviewing PRs that add or modify a payment Provider in yansongda/pay - covers plugin pipeline, multi-tenant safety, signature verification, docs, and naming conventions.
kanchengw/cnllm
Guides Stripe integration decisions — API selection (Checkout Sessions vs PaymentIntents), Connect platform setup (Accounts v2, controller properties), billing/subscriptions, Treasury financial…
kid-sid/claude-spellbook
A skill your agent uses when building or reviewing UI components for keyboard and screen reader compatibility, adding ARIA to custom widgets, auditing a page for WCAG AA conformance, or preparing…
kid-sid/claude-spellbook
A skill your agent uses when building, wiring, or debugging an Agentex agent — choosing agent type, configuring acp.py and manifest.yaml, using adk.messages or adk.state, or resolving…
kid-sid/claude-spellbook
A skill your agent uses when building production LLM applications — designing RAG pipelines, choosing vector databases, implementing agent orchestration, optimizing cost, or adding AI safety…
kid-sid/claude-spellbook
A skill your agent uses when building or refactoring Angular applications — choosing between signals, RxJS, and NgRx for state, configuring routing with guards and lazy loading, optimizing change…
kid-sid/claude-spellbook
A skill your agent uses when designing new REST endpoints, reviewing an existing API contract, adding pagination or filtering, planning a versioning strategy, or building a public or partner-facing…
kid-sid/claude-spellbook
A skill your agent uses when implementing login flows, issuing or validating JWTs, setting up OAuth2/OIDC with a provider, designing role-based or attribute-based access control, securing API…
Categories
A skill your agent uses when designing a webhook delivery system, implementing HMAC signature verification on a receiver, handling retries and failures on the sender side, building idempotent…. Webhooks is an agent skill from kid-sid/claude-spellbook. Use when designing a webhook delivery system, implementing HMAC signature verification on a receiver, handling retries and failures on the sender side, building idempotent webhook consumers, or testing webhook integrations locally.
Webhooks fits situations like: designing a webhook delivery system; implementing HMAC signature verification on a receiver; handling retries and failures on the sender side; building idempotent webhook consumers.
Run `npx skills add kid-sid/claude-spellbook --skill webhooks -a claude-code`. Or copy the skill folder (skills/webhooks in kid-sid/claude-spellbook) into .claude/skills/webhooks in your project. Claude Code loads it when a task matches its description.
Run `npx skills add kid-sid/claude-spellbook --skill webhooks -a codex`. Or copy the skill folder (skills/webhooks in kid-sid/claude-spellbook) into .agents/skills/webhooks in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add kid-sid/claude-spellbook --skill webhooks -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/webhooks, .gemini/skills/webhooks, .github/skills/webhooks and .opencode/skills/webhooks in your project.
Going by SKILL.md and its folder, Webhooks needs the command-line tools its instructions call (stripe, npx, ngrok and curl) and credentials named STRIPE_SECRET. Our summary lists: Python 3; Node.js; A credential in STRIPE_SECRET.
SKILL.md contains no URLs. Its commands use npx and curl, which can reach the network depending on how they are called. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Webhooks is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 3.2k tokens (SKILL.md is roughly 13k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Webhooks: Novu Design Workflow (novuhq/novu, 40k stars), Golive (mikehasa/golive-skill, 1.2k stars), Stripe Apps (fossasia/eventyay, 1.7k stars) and Dingtalk Message (agentscope-ai/ReMe, 3.6k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
kid-sid (a GitHub user) maintains it in kid-sid/claude-spellbook, which has 189 GitHub stars. The repository holds 54 skills in this directory. The repository was last updated on August 5, 2026.
Source: kid-sid/claude-spellbook on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.