encore-webhook

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Encore Webhook Endpoints

Encore Webhook 端点

Instructions

说明

Use
api.raw(...)
to receive inbound webhooks from third-party services. Raw endpoints give you direct access to the Node.js-style request and response objects, which you need for signature verification (the verification typically requires the unparsed raw body).
使用
api.raw(...)
接收第三方服务发送的入站 Webhook。Raw endpoint 可直接访问 Node.js 风格的请求和响应对象,这对于签名验证是必需的,因为验证过程通常需要使用未经解析的原始正文。

1. Import

1. 导入

typescript
import { api } from "encore.dev/api";
typescript
import { api } from "encore.dev/api";

2. Define the endpoint with
api.raw

2. 使用
api.raw
定义端点

typescript
export const stripeWebhook = api.raw(
  { expose: true, path: "/webhooks/stripe", method: "POST" },
  async (req, res) => {
    const sig = req.headers["stripe-signature"];
    // Read raw body
    const chunks: Buffer[] = [];
    for await (const chunk of req) chunks.push(chunk);
    const rawBody = Buffer.concat(chunks).toString("utf8");

    // Verify signature, parse event...

    res.writeHead(200, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ received: true }));
  }
);
typescript
export const stripeWebhook = api.raw(
  { expose: true, path: "/webhooks/stripe", method: "POST" },
  async (req, res) => {
    const sig = req.headers["stripe-signature"];
    // 读取原始正文
    const chunks: Buffer[] = [];
    for await (const chunk of req) chunks.push(chunk);
    const rawBody = Buffer.concat(chunks).toString("utf8");

    // 验证签名、解析事件……

    res.writeHead(200, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ received: true }));
  }
);

3. Verify the signature

3. 验证签名

Most providers sign webhooks. Read the secret with
secret(...)
from
encore.dev/config
(see the
encore-secret
skill) and verify before trusting the payload:
typescript
import { secret } from "encore.dev/config";
const stripeWebhookSecret = secret("StripeWebhookSecret");

// inside handler:
import Stripe from "stripe";
const stripe = new Stripe(stripeApiKey());
const event = stripe.webhooks.constructEvent(rawBody, sig, stripeWebhookSecret());
For GitHub, verify the HMAC-SHA256 in the
X-Hub-Signature-256
header against the raw body using your webhook secret.
大多数提供商会为 Webhook 签名。使用
encore.dev/config
中的
secret(...)
读取密钥(参见
encore-secret
skill),并在信任载荷前完成验证:
typescript
import { secret } from "encore.dev/config";
const stripeWebhookSecret = secret("StripeWebhookSecret");

// 在 handler 内:
import Stripe from "stripe";
const stripe = new Stripe(stripeApiKey());
const event = stripe.webhooks.constructEvent(rawBody, sig, stripeWebhookSecret());
对于 GitHub,应使用 Webhook 密钥对原始正文验证
X-Hub-Signature-256
请求头中的 HMAC-SHA256 签名。

Common providers

常见提供商

ProviderSignature headerVerification
Stripe
Stripe-Signature
stripe.webhooks.constructEvent(rawBody, sig, secret)
GitHub
X-Hub-Signature-256
HMAC-SHA256 over the raw body
Slack
X-Slack-Signature
HMAC-SHA256 over
v0:{timestamp}:{rawBody}
Shopify
X-Shopify-Hmac-Sha256
HMAC-SHA256 (base64) over the raw body
Twilio
X-Twilio-Signature
HMAC-SHA1 over URL + sorted form fields
提供商签名请求头验证方式
Stripe
Stripe-Signature
stripe.webhooks.constructEvent(rawBody, sig, secret)
GitHub
X-Hub-Signature-256
对原始正文执行 HMAC-SHA256
Slack
X-Slack-Signature
对
v0:{timestamp}:{rawBody}
执行 HMAC-SHA256
Shopify
X-Shopify-Hmac-Sha256
对原始正文执行 HMAC-SHA256(base64)
Twilio
X-Twilio-Signature
对 URL 和排序后的表单字段执行 HMAC-SHA1

Always respond quickly

始终快速响应

Webhook senders retry on non-2xx or slow responses. Acknowledge with a 2xx as soon as the payload is verified, then enqueue the actual work via Pub/Sub (see
encore-pubsub
) instead of doing it in the request handler.
typescript
import { Topic } from "encore.dev/pubsub";

interface StripeEvent { id: string; type: string; data: unknown; }
const stripeEvents = new Topic<StripeEvent>("stripe-events", {
  deliveryGuarantee: "at-least-once",
});

// inside the raw handler, after verification:
await stripeEvents.publish({ id: event.id, type: event.type, data: event.data });
res.writeHead(200); res.end();
当响应不是 2xx 或响应过慢时,Webhook 发送方会进行重试。验证载荷后应立即返回 2xx 以确认接收,然后通过 Pub/Sub(参见
encore-pubsub
)将实际工作加入队列,而不要在请求处理器中执行。
typescript
import { Topic } from "encore.dev/pubsub";

interface StripeEvent { id: string; type: string; data: unknown; }
const stripeEvents = new Topic<StripeEvent>("stripe-events", {
  deliveryGuarantee: "at-least-once",
});

// 在 raw handler 内,完成验证后:
await stripeEvents.publish({ id: event.id, type: event.type, data: event.data });
res.writeHead(200); res.end();

Guidelines

指南

  • Use
    api.raw
    only for webhooks and other low-level HTTP integrations.
  • Always verify the provider's signature before trusting the payload.
  • Always respond 2xx fast — push slow work onto Pub/Sub.
  • Store the signing secret with
    secret(...)
    ; never inline it.
  • For typed JSON endpoints in your own service, use plain
    api(...)
    from the
    encore-api
    skill.
  • 仅将
    api.raw
    用于 Webhook 和其他底层 HTTP 集成。
  • 信任提供商载荷前,始终验证其签名。
  • 始终快速返回 2xx,并将耗时工作推送到 Pub/Sub。
  • 使用
    secret(...)
    存储签名密钥;切勿将其内联到代码中。
  • 对于服务内部需要类型安全的 JSON 端点,请使用
    encore-api
    skill 中的普通
    api(...)
    。