Token导航 LogoToken导航TokenDH.com
开发执行命令github未标认证来源可访问许可证需确认审计提醒

webhook-tester网络钩子测试器

Agent Skill

用于辅助测试设计、自动化测试、用例整理和回归验证。它适合让 Agent 编写单元测试、端到端测试、测试计划或根据失败日志定位问题。使用时需要确认项目测试框架、运行命令和夹具数据,避免为了通过测试而改坏真实逻辑;涉及浏览器或外部服务时,应区分本地模拟、测试环境和生产环境。

总安装

360

周安装

15

GitHub Stars

26

下载量

120
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:webhook-tester(网络钩子测试器)
来源仓库:https://github.com/curiouslearner/devkit
仓库路径:skills/webhook-tester
安装命令:
npx skills add https://github.com/curiouslearner/devkit --skill webhook-tester
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/curiouslearner/devkit --skill webhook-tester

简介

用于辅助测试设计、自动化测试、用例整理和回归验证。

  • 适合让 Agent 编写单元测试、端到端测试、测试计划或根据失败日志定位问题。
  • 使用时需要确认项目测试框架、运行命令和夹具数据, 避免为了通过测试而改坏真实逻辑;webhook-tester 属于开发类 Skill,可作为该场景下的辅助能力补充。
  • 涉及浏览器或外部服务时,应区分本地模拟、测试环境和生产环境。

SKILL.md

Webhook Tester Skill

Test webhook integrations locally with tunneling, inspection, and debugging tools.

Instructions

You are a webhook testing expert. When invoked:

  1. Local Webhook Testing:

- Set up local webhook receivers - Expose localhost to internet using tunnels - Capture and inspect webhook payloads - Verify webhook signatures - Test retry mechanisms

  1. Debugging Webhooks:

- Inspect request headers and body - Validate webhook signatures - Test different payload formats - Simulate webhook failures - Log and replay webhooks

  1. Integration Testing:

- Test webhook delivery - Verify idempotency - Test retry logic - Validate error handling - Performance testing

  1. Security Validation:

- Verify signature validation - Test HTTPS requirements - Validate origin checking - Test replay attack prevention

Usage Examples

@webhook-tester
@webhook-tester --setup-tunnel
@webhook-tester --inspect
@webhook-tester --verify-signature
@webhook-tester --replay

Tunneling Tools

ngrok (Most Popular)

Basic Setup

# Install ngrok
# Download from https://ngrok.com/download
# Or use package manager
brew install ngrok/ngrok/ngrok  # macOS
choco install ngrok             # Windows

# Authenticate (get token from ngrok.com)
ngrok config add-authtoken YOUR_TOKEN

# Start tunnel to localhost:3000
ngrok http 3000

# Custom subdomain (requires paid plan)
ngrok http 3000 --subdomain=myapp

# Multiple ports
ngrok http 3000 3001

# Use specific region
ngrok http 3000 --region=us

# Enable inspection UI
ngrok http 3000 --inspect=true

ngrok Configuration File

# ~/.ngrok2/ngrok.yml
version: "2"
authtoken: YOUR_TOKEN

tunnels:
  api:
    addr: 3000
    proto: http
    subdomain: myapi

  webhooks:
    addr: 4000
    proto: http
    subdomain: webhooks

  web:
    addr: 8080
    proto: http
    bind_tls: true

# Start all tunnels
ngrok start --all

# Start specific tunnel
ngrok start api

ngrok API

// Using ngrok programmatically
const ngrok = require('ngrok');

async function startTunnel() {
  const url = await ngrok.connect({
    addr: 3000,
    region: 'us',
    onStatusChange: status => console.log('Status:', status)
  });

  console.log('Tunnel URL:', url);
  // Use this URL as webhook endpoint
  return url;
}

// Cleanup
async function stopTunnel() {
  await ngrok.disconnect();
  await ngrok.kill();
}

Cloudflare Tunnel (Free, No Account Required)

# Install
brew install cloudflare/cloudflare/cloudflared  # macOS
# Or download from cloudflare.com

# Quick tunnel (no auth required)
cloudflared tunnel --url http://localhost:3000

# Output will be: https://random-words.trycloudflare.com

localtunnel

# Install
npm install -g localtunnel

# Start tunnel
lt --port 3000

# Custom subdomain (may not be available)
lt --port 3000 --subdomain myapp

# Use localtunnel programmatically
const localtunnel = require('localtunnel');

const tunnel = await localtunnel({ port: 3000 });
console.log('Tunnel URL:', tunnel.url);

tunnel.on('close', () => {
  console.log('Tunnel closed');
});

VS Code Port Forwarding

# In VS Code with GitHub account
# 1. Open Terminal
# 2. Click "Ports" tab
# 3. Click "Forward a Port"
# 4. Enter port number (e.g., 3000)
# 5. Share the public URL

Webhook Receiver Setup

Express.js Webhook Endpoint

const express = require('express');
const crypto = require('crypto');

const app = express();

// Raw body parser for signature verification
app.use(express.json({
  verify: (req, res, buf) => {
    req.rawBody = buf.toString();
  }
}));

// Webhook endpoint
app.post('/webhooks/github', (req, res) => {
  console.log('Received webhook from GitHub');
  console.log('Headers:', req.headers);
  console.log('Body:', req.body);

  // Verify signature
  const signature = req.headers['x-hub-signature-256'];
  const secret = process.env.WEBHOOK_SECRET;

  if (!verifyGitHubSignature(req.rawBody, signature, secret)) {
    console.error('Invalid signature');
    return res.status(401).send('Invalid signature');
  }

  // Process webhook
  const event = req.headers['x-github-event'];
  handleGitHubEvent(event, req.body);

  // Always respond quickly (GitHub expects response within 10s)
  res.status(200).send('OK');
});

function verifyGitHubSignature(payload, signature, secret) {
  if (!signature) return false;

  const hmac = crypto.createHmac('sha256', secret);
  const digest = 'sha256=' + hmac.update(payload).digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(digest)
  );
}

function handleGitHubEvent(event, payload) {
  switch (event) {
    case 'push':
      console.log('Push event:', payload.ref);
      break;
    case 'pull_request':
      console.log('PR event:', payload.action);
      break;
    default:
      console.log('Unhandled event:', event);
  }
}

// Stripe webhook
app.post('/webhooks/stripe', (req, res) => {
  const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
  const sig = req.headers['stripe-signature'];

  let event;
  try {
    event = stripe.webhooks.constructEvent(
      req.rawBody,
      sig,
      process.env.STRIPE_WEBHOOK_SECRET
    );
  } catch (err) {
    console.error('Webhook signature verification failed:', err.message);
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  // Handle the event
  switch (event.type) {
    case 'payment_intent.succeeded':
      const paymentIntent = event.data.object;
      console.log('PaymentIntent succeeded:', paymentIntent.id);
      break;
    case 'payment_intent.failed':
      console.log('PaymentIntent failed');
      break;
    default:
      console.log(`Unhandled event type ${event.type}`);
  }

  res.json({ received: true });
});

// Generic webhook logger
app.post('/webhooks/:service', (req, res) => {
  const { service } = req.params;

  console.log(`\n${'='.repeat(50)}`);
  console.log(`Webhook received: ${service}`);
  console.log(`Timestamp: ${new Date().toISOString()}`);
  console.log(`${'='.repeat(50)}`);

  console.log('\nHeaders:');
  Object.entries(req.headers).forEach(([key, value]) => {
    console.log(`  ${key}: ${value}`);
  });

  console.log('\nBody:');
  console.log(JSON.stringify(req.body, null, 2));

  console.log(`${'='.repeat(50)}\n`);

  res.status(200).json({ received: true });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Webhook receiver listening on port ${PORT}`);
});

Python Flask Webhook Receiver

from flask import Flask, request, jsonify
import hmac
import hashlib
import os

app = Flask(__name__)

@app.route('/webhooks/github', methods=['POST'])
def github_webhook():
    # Verify signature
    signature = request.headers.get('X-Hub-Signature-256')
    secret = os.getenv('WEBHOOK_SECRET')

    if not verify_github_signature(request.data, signature, secret):
        return 'Invalid signature', 401

    event = request.headers.get('X-GitHub-Event')
    payload = request.json

    print(f'Received {event} event')
    print(f'Payload: {payload}')

    # Process event
    handle_github_event(event, payload)

    return 'OK', 200

def verify_github_signature(payload, signature, secret):
    if not signature:
        return False

    mac = hmac.new(
        secret.encode(),
        msg=payload,
        digestmod=hashlib.sha256
    )
    expected = 'sha256=' + mac.hexdigest()

    return hmac.compare_digest(expected, signature)

def handle_github_event(event, payload):
    if event == 'push':
        print(f"Push to {payload['ref']}")
    elif event == 'pull_request':
        print(f"PR {payload['action']}")

@app.route('/webhooks/<service>', methods=['POST'])
def generic_webhook(service):
    print(f'\n{"=" * 50}')
    print(f'Webhook received: {service}')
    print(f'{"=" * 50}')

    print('\nHeaders:')
    for key, value in request.headers:
        print(f'  {key}: {value}')

    print('\nBody:')
    print(request.get_data(as_text=True))

    return jsonify({'received': True}), 200

if __name__ == '__main__':
    app.run(port=3000)

Webhook Testing Tools

webhook.site (Online Tool)

# 1. Visit https://webhook.site
# 2. Get unique URL (e.g., https://webhook.site/abc-123)
# 3. Use this URL as webhook endpoint
# 4. View all incoming requests in real-time

# Features:
# - Unique URL per session
# - View request headers and body
# - Custom response configuration
# - Request history
# - Share URL with team

Postman

// 1. Create Mock Server in Postman
// 2. Add webhook endpoint
// 3. Configure response
// 4. Use mock URL as webhook endpoint

// Example Mock Server Response
{
  "statusCode": 200,
  "body": {
    "received": true,
    "timestamp": "{{$timestamp}}"
  }
}

Webhook Testing CLI

// webhook-cli.js
const express = require('express');
const chalk = require('chalk');

class WebhookTester {
  constructor(port = 3000) {
    this.app = express();
    this.port = port;
    this.requests = [];

    this.setupMiddleware();
    this.setupRoutes();
  }

  setupMiddleware() {
    this.app.use(express.json({
      verify: (req, res, buf) => {
        req.rawBody = buf.toString();
      }
    }));
  }

  setupRoutes() {
    // Catch all webhook requests
    this.app.all('/webhooks/*', (req, res) => {
      const webhook = {
        timestamp: new Date().toISOString(),
        method: req.method,
        path: req.path,
        headers: req.headers,
        body: req.body,
        query: req.query
      };

      this.requests.push(webhook);
      this.logWebhook(webhook);

      res.status(200).json({ received: true });
    });
  }

  logWebhook(webhook) {
    console.log(chalk.blue('\n' + '='.repeat(60)));
    console.log(chalk.green('Webhook Received'));
    console.log(chalk.blue('='.repeat(60)));

    console.log(chalk.yellow('\nTimestamp:'), webhook.timestamp);
    console.log(chalk.yellow('Method:'), webhook.method);
    console.log(chalk.yellow('Path:'), webhook.path);

    console.log(chalk.yellow('\nHeaders:'));
    Object.entries(webhook.headers).forEach(([key, value]) => {
      console.log(`  ${chalk.gray(key)}: ${value}`);
    });

    if (Object.keys(webhook.query).length > 0) {
      console.log(chalk.yellow('\nQuery:'));
      console.log(JSON.stringify(webhook.query, null, 2));
    }

    console.log(chalk.yellow('\nBody:'));
    console.log(JSON.stringify(webhook.body, null, 2));

    console.log(chalk.blue('='.repeat(60) + '\n'));
  }

  start() {
    this.app.listen(this.port, () => {
      console.log(chalk.green(`\nWebhook tester running on http://localhost:${this.port}`));
      console.log(chalk.gray('Waiting for webhooks...\n'));
    });
  }

  getHistory() {
    return this.requests;
  }

  clearHistory() {
    this.requests = [];
    console.log(chalk.yellow('History cleared'));
  }
}

// Usage
const tester = new WebhookTester(3000);
tester.start();

Testing Webhook Signatures

GitHub Webhook Signature

const crypto = require('crypto');

function verifyGitHubWebhook(payload, signature, secret) {
  if (!signature || !signature.startsWith('sha256=')) {
    return false;
  }

  const hmac = crypto.createHmac('sha256', secret);
  const digest = 'sha256=' + hmac.update(payload).digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(digest)
  );
}

// Test
const payload = JSON.stringify({ test: 'data' });
const secret = 'my-webhook-secret';
const signature = 'sha256=' + crypto
  .createHmac('sha256', secret)
  .update(payload)
  .digest('hex');

console.log('Valid:', verifyGitHubWebhook(payload, signature, secret));

Stripe Webhook Signature

const stripe = require('stripe')('sk_test_...');

app.post('/webhooks/stripe', async (req, res) => {
  const sig = req.headers['stripe-signature'];
  const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;

  let event;

  try {
    event = stripe.webhooks.constructEvent(
      req.rawBody,
      sig,
      webhookSecret
    );
  } catch (err) {
    console.error('Webhook signature verification failed:', err.message);
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  // Process the event
  console.log('Event:', event.type);
  res.json({ received: true });
});

Shopify HMAC Verification

const crypto = require('crypto');

function verifyShopifyWebhook(body, hmacHeader, secret) {
  const hash = crypto
    .createHmac('sha256', secret)
    .update(body, 'utf8')
    .digest('base64');

  return crypto.timingSafeEqual(
    Buffer.from(hash),
    Buffer.from(hmacHeader)
  );
}

app.post('/webhooks/shopify', (req, res) => {
  const hmac = req.headers['x-shopify-hmac-sha256'];
  const secret = process.env.SHOPIFY_SECRET;

  if (!verifyShopifyWebhook(req.rawBody, hmac, secret)) {
    return res.status(401).send('Invalid signature');
  }

  res.status(200).send('OK');
});

Automated Webhook Testing

Jest Tests

const request = require('supertest');
const app = require('./app');
const crypto = require('crypto');

describe('Webhook Tests', () => {
  const webhookSecret = 'test-secret';

  function generateSignature(payload) {
    return 'sha256=' + crypto
      .createHmac('sha256', webhookSecret)
      .update(JSON.stringify(payload))
      .digest('hex');
  }

  describe('POST /webhooks/github', () => {
    test('should accept valid webhook', async () => {
      const payload = {
        ref: 'refs/heads/main',
        commits: []
      };

      const signature = generateSignature(payload);

      const response = await request(app)
        .post('/webhooks/github')
        .set('X-Hub-Signature-256', signature)
        .set('X-GitHub-Event', 'push')
        .send(payload);

      expect(response.status).toBe(200);
    });

    test('should reject invalid signature', async () => {
      const payload = { test: 'data' };

      const response = await request(app)
        .post('/webhooks/github')
        .set('X-Hub-Signature-256', 'invalid')
        .set('X-GitHub-Event', 'push')
        .send(payload);

      expect(response.status).toBe(401);
    });

    test('should reject missing signature', async () => {
      const payload = { test: 'data' };

      const response = await request(app)
        .post('/webhooks/github')
        .set('X-GitHub-Event', 'push')
        .send(payload);

      expect(response.status).toBe(401);
    });
  });
});

Webhook Replay and Debugging

Request Storage

const fs = require('fs').promises;
const path = require('path');

class WebhookStorage {
  constructor(storageDir = './webhooks') {
    this.storageDir = storageDir;
  }

  async saveWebhook(webhook) {
    const filename = `${Date.now()}-${webhook.path.replace(/\//g, '-')}.json`;
    const filepath = path.join(this.storageDir, filename);

    await fs.mkdir(this.storageDir, { recursive: true });
    await fs.writeFile(filepath, JSON.stringify(webhook, null, 2));

    console.log('Webhook saved:', filepath);
  }

  async loadWebhook(filename) {
    const filepath = path.join(this.storageDir, filename);
    const content = await fs.readFile(filepath, 'utf8');
    return JSON.parse(content);
  }

  async replayWebhook(filename) {
    const webhook = await this.loadWebhook(filename);

    const response = await fetch(`http://localhost:3000${webhook.path}`, {
      method: webhook.method,
      headers: webhook.headers,
      body: JSON.stringify(webhook.body)
    });

    console.log('Replayed webhook:', filename);
    console.log('Response:', response.status);
  }
}

Webhook Retry Testing

Retry Simulation

app.post('/webhooks/test-retry', async (req, res) => {
  const attemptNumber = parseInt(req.headers['x-attempt'] || '1');
  const maxAttempts = 3;

  console.log(`Attempt ${attemptNumber}/${maxAttempts}`);

  // Fail first 2 attempts
  if (attemptNumber < maxAttempts) {
    console.log('Simulating failure');
    return res.status(500).send('Temporary error');
  }

  console.log('Success on final attempt');
  res.status(200).send('OK');
});

// Retry logic (sender side)
async function sendWebhookWithRetry(url, payload, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-Attempt': attempt.toString()
        },
        body: JSON.stringify(payload)
      });

      if (response.ok) {
        console.log(`Webhook delivered on attempt ${attempt}`);
        return response;
      }

      console.log(`Attempt ${attempt} failed: ${response.status}`);
    } catch (error) {
      console.log(`Attempt ${attempt} error:`, error.message);
    }

    // Exponential backoff
    if (attempt < maxRetries) {
      const delay = Math.pow(2, attempt) * 1000;
      console.log(`Waiting ${delay}ms before retry...`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }

  throw new Error('All webhook delivery attempts failed');
}

Best Practices

Webhook Receiver

  • Respond quickly (within 10 seconds)
  • Always return 200 for valid requests
  • Process webhooks asynchronously
  • Implement idempotency
  • Verify signatures
  • Log all webhooks for debugging

Security

  • Always verify webhook signatures
  • Use HTTPS endpoints
  • Validate webhook origin
  • Implement rate limiting
  • Store secrets securely
  • Check for replay attacks

Error Handling

  • Handle missing/invalid signatures gracefully
  • Log all errors
  • Implement retry logic with exponential backoff
  • Alert on repeated failures
  • Monitor webhook health

Testing

  • Test signature verification
  • Simulate failures and retries
  • Test idempotency
  • Verify error handling
  • Load test webhook endpoints
  • Test with real payloads

Common Webhook Providers

GitHub

Signature Header: X-Hub-Signature-256
Event Header: X-GitHub-Event
Algorithm: HMAC SHA-256

Stripe

Signature Header: Stripe-Signature
Algorithm: HMAC SHA-256 (special format)
Test Mode: Use Stripe CLI

Shopify

Signature Header: X-Shopify-Hmac-SHA256
Algorithm: HMAC SHA-256 (base64)
Topic Header: X-Shopify-Topic

Twilio

Signature Header: X-Twilio-Signature
Algorithm: HMAC SHA-1
Validation: Special URL + params

Slack

Signature Header: X-Slack-Signature
Timestamp Header: X-Slack-Request-Timestamp
Algorithm: HMAC SHA-256

Notes

  • Use tunneling tools (ngrok, cloudflared) for local testing
  • Always verify webhook signatures in production
  • Respond to webhooks quickly (< 10s)
  • Process webhooks asynchronously
  • Implement idempotency using webhook IDs
  • Log all webhooks for debugging
  • Test retry mechanisms
  • Monitor webhook delivery failures
  • Use webhook testing tools during development
  • Store webhook secrets securely
  • Implement proper error handling
  • Test with real payloads from providers

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

33.41%
按下载量换算40

Claude

28.45%
按下载量换算34

Cursor

21.42%
按下载量换算26

Gemini CLI

9.03%
按下载量换算11

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

可疑

权限和风险

执行命令

安装流程涉及命令执行,可能通过 npx skills add https://github.com/curiouslearner/devkit --skill webhook-tester 联网下载 Skill 或依赖。用户安装前应确认命令来源、仓库内容和执行环境。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在 warning/failed 结果,不能写成本站确认安全。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills