本文目录导读:

- 基础 Webhook 接收端
- Webhook 签名验证
- 使用框架处理 Webhook
- 异步处理 Webhook
- Webhook 发送端
- Webhook 事件管理
- 安全最佳实践
- 监控与日志
- 完整示例:GitHub Webhook 集成
- 测试工具
- 最佳实践清单
我来系统地介绍 PHP 在 Webhook 生态中的实践方法,Webhook 本质上是 HTTP 回调,PHP 作为服务端语言非常适合处理这类请求。
基础 Webhook 接收端
最简单的接收示例
<?php
// webhook-receiver.php
// 获取原始请求体
$payload = file_get_contents('php://input');
$data = json_decode($payload, true);
// 记录日志
error_log('收到 Webhook: ' . $payload);
// 验证请求
if (empty($data)) {
http_response_code(400);
echo json_encode(['error' => 'Invalid payload']);
exit;
}
// 处理业务逻辑
try {
// 你的业务处理代码
processWebhook($data);
// 返回成功响应
http_response_code(200);
echo json_encode(['status' => 'success']);
} catch (Exception $e) {
error_log('Webhook 处理失败: ' . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
function processWebhook($data) {
// 处理逻辑
}
?>
Webhook 签名验证
GitHub Webhook 验证示例
<?php
class GitHubWebhookVerifier {
private $secret;
public function __construct($secret) {
$this->secret = $secret;
}
public function verify($payload, $signature) {
if (!$signature) {
return false;
}
// 计算 HMAC 签名
$expected = 'sha256=' . hash_hmac('sha256', $payload, $this->secret);
// 使用 hash_equals 防止时序攻击
return hash_equals($expected, $signature);
}
}
// 使用示例
$verifier = new GitHubWebhookVerifier('your_secret_key');
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_HUB_SIGNATURE_256'] ?? '';
if (!$verifier->verify($payload, $signature)) {
http_response_code(401);
exit('Invalid signature');
}
?>
Stripe Webhook 验证
<?php
require 'vendor/autoload.php';
use Stripe\Webhook;
class StripeWebhookHandler {
public function handle($payload, $sigHeader, $endpointSecret) {
try {
$event = Webhook::constructEvent(
$payload, $sigHeader, $endpointSecret
);
// 处理不同事件类型
switch ($event->type) {
case 'payment_intent.succeeded':
$paymentIntent = $event->data->object;
$this->handlePaymentSuccess($paymentIntent);
break;
case 'payment_intent.payment_failed':
$this->handlePaymentFailed($event->data->object);
break;
default:
echo 'Received unknown event type: ' . $event->type;
}
http_response_code(200);
} catch (\UnexpectedValueException $e) {
http_response_code(400);
exit('Invalid payload');
} catch (\Stripe\Exception\SignatureVerificationException $e) {
http_response_code(400);
exit('Invalid signature');
}
}
}
?>
使用框架处理 Webhook
Laravel 示例
<?php
// routes/api.php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\WebhookController;
Route::post('/webhook/github', [WebhookController::class, 'github']);
Route::post('/webhook/stripe', [WebhookController::class, 'stripe']);
?>
<?php
// app/Http/Controllers/WebhookController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class WebhookController extends Controller
{
public function github(Request $request)
{
// 验证签名
$signature = $request->header('X-Hub-Signature-256');
$payload = $request->getContent();
if (!$this->verifyGithubSignature($payload, $signature)) {
return response()->json(['error' => 'Unauthorized'], 401);
}
// 处理事件
$event = $request->header('X-GitHub-Event');
$data = $request->all();
// 异步处理
dispatch(new ProcessGithubWebhook($event, $data));
return response()->json(['status' => 'received']);
}
public function stripe(Request $request)
{
$payload = $request->getContent();
$sigHeader = $request->header('Stripe-Signature');
try {
$event = \Stripe\Webhook::constructEvent(
$payload,
$sigHeader,
config('services.stripe.webhook_secret')
);
// 处理事件
$this->handleStripeEvent($event);
return response()->json(['status' => 'success']);
} catch (\Exception $e) {
return response()->json(['error' => $e->getMessage()], 400);
}
}
}
?>
异步处理 Webhook
使用队列处理
<?php
// 使用 Redis 或 RabbitMQ
class AsyncWebhookProcessor {
private $redis;
public function __construct($redis) {
$this->redis = $redis;
}
public function enqueue($webhookName, $data) {
// 推入队列
$this->redis->lpush('webhook_queue', json_encode([
'name' => $webhookName,
'data' => $data,
'timestamp' => time()
]));
}
public function processQueue() {
// 后台 worker 处理
while ($job = $this->redis->rpop('webhook_queue')) {
$webhook = json_decode($job, true);
$this->processWebhook($webhook['name'], $webhook['data']);
}
}
}
?>
Webhook 发送端
发送 Webhook 到其他服务
<?php
class WebhookSender {
private $httpClient;
public function __construct() {
$this->httpClient = new \GuzzleHttp\Client([
'timeout' => 10,
'verify' => false // 生产环境不要这样
]);
}
public function sendWebhook($url, $event, $data, $secret = null) {
$payload = json_encode([
'event' => $event,
'data' => $data,
'timestamp' => time()
]);
$headers = [
'Content-Type' => 'application/json',
'User-Agent' => 'MyApp-Webhook/1.0'
];
// 如果配置了 secret,添加签名
if ($secret) {
$signature = hash_hmac('sha256', $payload, $secret);
$headers['X-Webhook-Signature'] = $signature;
$headers['X-Webhook-Signature-256'] = 'sha256=' . $signature;
}
try {
$response = $this->httpClient->post($url, [
'headers' => $headers,
'body' => $payload,
'allow_redirects' => true
]);
return $response->getStatusCode() === 200;
} catch (\Exception $e) {
error_log('Webhook 发送失败: ' . $e->getMessage());
// 重试逻辑
$this->retryWebhook($url, $event, $data, $secret);
return false;
}
}
private function retryWebhook($url, $event, $data, $secret, $retries = 3) {
if ($retries > 0) {
sleep(5 * (4 - $retries)); // 退避策略
$this->sendWebhook($url, $event, $data, $secret, $retries - 1);
}
}
}
?>
Webhook 事件管理
事件订阅管理
<?php
class WebhookSubscriptionManager {
private $db;
public function __construct(PDO $db) {
$this->db = $db;
}
public function subscribe($userId, $event, $callbackUrl) {
$stmt = $this->db->prepare(
'INSERT INTO webhook_subscriptions (user_id, event, callback_url)
VALUES (?, ?, ?)'
);
return $stmt->execute([$userId, $event, $callbackUrl]);
}
public function getSubscribers($event) {
$stmt = $this->db->prepare(
'SELECT * FROM webhook_subscriptions WHERE event = ? AND active = 1'
);
$stmt->execute([$event]);
return $stmt->fetchAll();
}
public function trigger($event, $data) {
$subscribers = $this->getSubscribers($event);
$sender = new WebhookSender();
foreach ($subscribers as $sub) {
// 异步发送
$sender->sendWebhook(
$sub['callback_url'],
$event,
$data,
$sub['secret']
);
}
}
}
?>
安全最佳实践
完整的 Webhook 安全防护
<?php
class SecureWebhookHandler {
private $ipWhitelist = [
'192.30.252.0/22', // GitHub
'185.199.108.0/22' // GitHub
];
public function handle($payload, $headers, $secret) {
// 1. IP 验证
if (!$this->checkIp($this->getClientIp())) {
return ['status' => 'error', 'message' => 'IP 不允许'];
}
// 2. 验证请求头
if (!$this->validateHeaders($headers)) {
return ['status' => 'error', 'message' => 'Header 验证失败'];
}
// 3. 签名验证
if (!$this->verifySignature($payload, $headers['X-Signature'] ?? '', $secret)) {
return ['status' => 'error', 'message' => '签名无效'];
}
// 4. 请求频率限制
if (!$this->checkRateLimit()) {
return ['status' => 'error', 'message' => '请求过于频繁'];
}
// 5. 幂等性检查
$requestId = $headers['X-Request-ID'] ?? '';
if ($this->isDuplicateRequest($requestId)) {
return ['status' => 'duplicate', 'message' => '重复请求'];
}
// 6. 业务处理
return $this->processPayload($payload);
}
private function checkIp($ip) {
// 实现 IP 白名单检查
foreach ($this->ipWhitelist as $cidr) {
if ($this->ipInRange($ip, $cidr)) {
return true;
}
}
return false;
}
private function verifySignature($payload, $signature, $secret) {
$expected = hash_hmac('sha256', $payload, $secret);
return hash_equals($expected, $signature);
}
private function isDuplicateRequest($requestId) {
// 使用 Redis 检查重复
$cacheKey = "webhook:request:$requestId";
if ($this->redis->exists($cacheKey)) {
return true;
}
$this->redis->setex($cacheKey, 300, 'seen');
return false;
}
}
?>
监控与日志
Webhook 监控系统
<?php
class WebhookMonitor {
private $logger;
private $metrics;
public function logRequest($id, $url, $status, $duration, $data = []) {
$logEntry = [
'id' => $id,
'url' => $url,
'status' => $status,
'duration' => $duration,
'timestamp' => date('Y-m-d H:i:s'),
'data' => $data
];
// 写入日志
$this->logger->info('Webhook 请求', $logEntry);
// 收集指标
$this->metrics->increment("webhook.status.$status");
$this->metrics->histogram('webhook.duration', $duration);
}
public function alertOnFailure($webhookId, $error) {
// 发送告警
$this->sendAlert([
'type' => 'webhook_failed',
'webhook_id' => $webhookId,
'error' => $error,
'time' => time()
]);
}
}
?>
完整示例:GitHub Webhook 集成
<?php
// github-webhook.php
require 'vendor/autoload.php';
class GitHubWebhookIntegration {
private $config;
public function __construct() {
$this->config = [
'secret' => getenv('GITHUB_WEBHOOK_SECRET'),
'log_file' => '/var/log/webhooks/github.log',
'token' => getenv('GITHUB_TOKEN')
];
}
public function handle() {
$headers = getallheaders();
$payload = file_get_contents('php://input');
$event = $headers['X-GitHub-Event'] ?? '';
$signature = $headers['X-Hub-Signature-256'] ?? '';
$delivery = $headers['X-GitHub-Delivery'] ?? '';
// 1. 验证签名
$valid = $this->verifySignature($payload, $signature);
if (!$valid) {
$this->log('Invalid signature', 'error');
http_response_code(401);
return;
}
// 2. 解析数据
$data = json_decode($payload, true);
// 3. 处理事件
$this->processEvent($event, $data);
// 4. 记录日志
$this->log("Webhook received: $event (delivery: $delivery)", 'info');
// 5. 返回响应
http_response_code(200);
echo json_encode(['status' => 'ok']);
}
private function verifySignature($payload, $signature) {
$expected = 'sha256=' . hash_hmac('sha256', $payload, $this->config['secret']);
return hash_equals($expected, $signature);
}
private function processEvent($event, $data) {
switch ($event) {
case 'push':
$this->handlePush($data);
break;
case 'pull_request':
$this->handlePullRequest($data);
break;
case 'issues':
$this->handleIssue($data);
break;
default:
$this->log("Unhandled event: $event", 'warning');
}
}
private function handlePush($data) {
// 部署逻辑
$branch = $this->getBranchName($data['ref']);
$repo = $data['repository']['full_name'];
$commit = $data['head_commit']['message'] ?? '';
$this->log("Push to $repo $branch: $commit");
// 触发部署
if ($branch === 'production') {
$this->triggerDeployment($repo, $commit);
}
}
private function handlePullRequest($data) {
$action = $data['action'];
$prNumber = $data['number'];
$title = $data['pull_request']['title'];
$this->log("PR #$prNumber: $action - $title");
// 自动合并已批准的 PR
if ($action === 'approved') {
$this->autoMerge($prNumber);
}
}
private function handleIssue($data) {
$action = $data['action'];
$issueNumber = $data['issue']['number'];
$title = $data['issue']['title'];
$this->log("Issue #$issueNumber: $action - $title");
// 对新 issue 自动回应
if ($action === 'opened') {
$this->autoRespondToIssue($issueNumber);
}
}
private function triggerDeployment($repo, $commit) {
// 调用部署 API
$url = "https://api.example.com/deploy";
$client = new \GuzzleHttp\Client();
$client->post($url, [
'json' => [
'repo' => $repo,
'commit' => $commit
]
]);
}
private function log($message, $level = 'info') {
$logLine = sprintf(
"[%s] [%s] %s\n",
date('Y-m-d H:i:s'),
strtoupper($level),
$message
);
file_put_contents($this->config['log_file'], $logLine, FILE_APPEND);
}
}
// 执行
$webhook = new GitHubWebhookIntegration();
$webhook->handle();
?>
测试工具
Webhook 测试客户端
<?php
// webhook-test-client.php
class WebhookTester {
public function sendTestWebhook($url, $event, $data = [], $secret = null) {
$payload = json_encode([
'event' => $event,
'data' => $data,
'test' => true,
'timestamp' => time()
]);
$headers = [
'Content-Type' => 'application/json',
'X-Test-Webhook' => 'true'
];
if ($secret) {
$headers['X-Signature'] = hash_hmac('sha256', $payload, $secret);
}
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => array_map(
function($key, $value) {
return "$key: $value";
},
array_keys($headers),
$headers
),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30
]);
$response = curl_exec($ch);
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return [
'status_code' => $statusCode,
'response' => json_decode($response, true)
];
}
}
?>
最佳实践清单
-
安全
- 始终验证签名
- 使用 HTTPS
- 实现 IP 白名单
- 添加请求频率限制
-
可靠性
- 实现重试机制
- 使用消息队列
- 记录所有请求日志
- 设置合理的超时时间
-
性能
- 异步处理耗时操作
- 返回 200 快速响应
- 使用缓存减少数据库查询
-
调试
- 详细的日志记录
- 提供测试端点
- 使用 webhook.site 等工具测试
这样就可以在 PHP 中构建健壮的 Webhook 系统了,根据具体需求,你可以选择相应的组件来构建完整的 Webhook 解决方案。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播