← Vytvářejte agenty na Cloudflare / agents / communication-channels
Webhooks
Přijímejte webhookové události z externích služeb a směrujte je k vyhrazeným instancím agentů. Každý zdroj webhooků (repozitář, zákazník, zařízení) může mít vlastního agenta s izolovaným stavem, trvalým úložištěm a připojeními klientů v reálném čase.
Rychlý start
import { Agent, getAgentByName, routeAgentRequest } from "agents";
export class WebhookAgent extends Agent {
async onRequest(request) {
if (request.method !== "POST") {
return new Response("Method not allowed", { status: 405 });
}
const rawBody = await request.text();
const signature = request.headers.get("X-Hub-Signature-256");
if (
!(await verifyGitHubWebhook(rawBody, signature, this.env.WEBHOOK_SECRET))
) {
return new Response("Invalid signature", { status: 401 });
}
let payload;
try {
payload = JSON.parse(rawBody);
} catch {
return new Response("Invalid payload", { status: 400 });
}
await this.processEvent(payload);
return new Response("OK");
}
async processEvent(payload) {
// Store event, update state, trigger actions...
}
}
async function verifyGitHubWebhook(rawBody, signature, secret) {
if (!signature || !/^sha256=[0-9a-f]{64}$/i.test(signature)) return false;
const encoder = new TextEncoder();
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(secret),
{ name: "HMAC", hash: "SHA-256" },
false,
["verify"],
);
const signatureBytes = Uint8Array.from(
signature.slice("sha256=".length).match(/.{2}/g) ?? [],
(byte) => Number.parseInt(byte, 16),
);
return crypto.subtle.verify(
"HMAC",
key,
signatureBytes,
encoder.encode(rawBody),
);
}
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (url.pathname === "/webhooks/github" && request.method === "POST") {
const rawBody = await request.clone().text();
const signature = request.headers.get("X-Hub-Signature-256");
if (
!(await verifyGitHubWebhook(rawBody, signature, env.WEBHOOK_SECRET))
) {
return new Response("Invalid signature", { status: 401 });
}
let payload;
try {
payload = JSON.parse(rawBody);
} catch {
return new Response("Invalid payload", { status: 400 });
}
const repository = payload.repository?.full_name;
if (!repository) {
return new Response("Missing repository", { status: 400 });
}
const agentName = repository.toLowerCase().replace(/\//g, "-");
const agent = await getAgentByName(env.WebhookAgent, agentName);
return agent.fetch(request);
}
return (
(await routeAgentRequest(request, env)) ||
new Response("Not found", { status: 404 })
);
},
};import { Agent, getAgentByName, routeAgentRequest } from "agents";
export class WebhookAgent extends Agent<Env> {
async onRequest(request: Request): Promise<Response> {
if (request.method !== "POST") {
return new Response("Method not allowed", { status: 405 });
}
const rawBody = await request.text();
const signature = request.headers.get("X-Hub-Signature-256");
if (
!(await verifyGitHubWebhook(
rawBody,
signature,
this.env.WEBHOOK_SECRET,
))
) {
return new Response("Invalid signature", { status: 401 });
}
let payload: unknown;
try {
payload = JSON.parse(rawBody);
} catch {
return new Response("Invalid payload", { status: 400 });
}
await this.processEvent(payload);
return new Response("OK");
}
private async processEvent(payload: unknown) {
// Store event, update state, trigger actions...
}
}
async function verifyGitHubWebhook(
rawBody: string,
signature: string | null,
secret: string,
): Promise<boolean> {
if (!signature || !/^sha256=[0-9a-f]{64}$/i.test(signature)) return false;
const encoder = new TextEncoder();
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(secret),
{ name: "HMAC", hash: "SHA-256" },
false,
["verify"],
);
const signatureBytes = Uint8Array.from(
signature.slice("sha256=".length).match(/.{2}/g) ?? [],
(byte) => Number.parseInt(byte, 16),
);
return crypto.subtle.verify(
"HMAC",
key,
signatureBytes,
encoder.encode(rawBody),
);
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === "/webhooks/github" && request.method === "POST") {
const rawBody = await request.clone().text();
const signature = request.headers.get("X-Hub-Signature-256");
if (
!(await verifyGitHubWebhook(
rawBody,
signature,
env.WEBHOOK_SECRET,
))
) {
return new Response("Invalid signature", { status: 401 });
}
let payload: { repository?: { full_name?: string } };
try {
payload = JSON.parse(rawBody);
} catch {
return new Response("Invalid payload", { status: 400 });
}
const repository = payload.repository?.full_name;
if (!repository) {
return new Response("Missing repository", { status: 400 });
}
const agentName = repository.toLowerCase().replace(/\//g, "-");
const agent = await getAgentByName(env.WebhookAgent, agentName);
return agent.fetch(request);
}
return (
(await routeAgentRequest(request, env)) ||
new Response("Not found", { status: 404 })
);
},
} satisfies ExportedHandler<Env>;Případy použití
Kombinace webhooků a agentů umožňuje vzory, ve kterých každá externí entita získá vlastní izolovanou stavovou instanci agenta.
Nástroje pro vývojáře
| Případ použití | Popis |
|---|---|
| GitHub Repo Monitor | Jeden agent na repozitář sledující commity, PR, issues a stars |
| Agent pro CI/CD pipeline | Reagujte na události sestavení a nasazení, upozorňujte na selhání a sledujte historii nasazení |
| Linear/Jira Tracker | Automatická triáž požadavků, přiřazování podle obsahu, sledování doby vyřešení |
E-commerce a platby
| Případ použití | Popis |
|---|---|
| Stripe Customer Agent | Jeden agent na zákazníka sledující platby, předplatná a spory |
| Shopify Order Agent | Životní cyklus objednávky od vytvoření po vyřízení se synchronizací skladu |
| Zúčtování plateb | Přiřadit události webhooku k interním záznamům, označit nesrovnalosti |
Komunikace a notifikace
| Případ použití | Popis |
|---|---|
| Twilio SMS/Voice | Konverzační agenti spouštění příchozími zprávami nebo hovory |
| Slack Bot | Reagovat na příkazy se lomítkem, kliknutí na tlačítka a interaktivní zprávy |
| Sledování e-mailů | Události doručení SendGrid/Mailgun, zpracování bounce zpráv, analytika zapojení |
IoT a infrastruktura
| Případ použití | Popis |
|---|---|
| Telemetrie zařízení | Jeden agent na zařízení zpracovávající streamy dat ze senzorů |
| Agregace upozornění | Shromažďujte upozornění z PagerDuty, Datadogu nebo vlastního monitorování |
| Domácí automatizace | Reagujte na spouštěče IFTTT/Zapier s perzistentním stavem |
Integrace SaaS
| Případ použití | Popis |
|---|---|
| Synchronizace CRM | Aktualizace kontaktů a obchodů v Salesforce/HubSpot |
| Agent pro kalendář | Oznámení a plánování událostí Google Calendar |
| Odesílání formulářů | Webhooky Typeform, Tally nebo vlastních formulářů s navazujícími akcemi |
Směrování webhooků agentům
Klíčový vzor spočívá v tom, že se nejprve ověří syrový požadavek ještě před jeho parsováním a teprve poté se z ověřených dat payloadu odvodí identita Agenta. Podpis těla neautentizuje nesouvisející segment URL adresy ani libovolnou hlavičku.
Extrahuje entitu z payloadu
Většina webhooků obsahuje identifikátor v payloadu:
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (request.method === "POST" && url.pathname === "/webhooks/github") {
const rawBody = await request.clone().text();
const signature = request.headers.get("X-Hub-Signature-256");
if (
!(await verifyGitHubWebhook(rawBody, signature, env.WEBHOOK_SECRET))
) {
return new Response("Invalid signature", { status: 401 });
}
let payload;
try {
payload = JSON.parse(rawBody);
} catch {
return new Response("Invalid payload", { status: 400 });
}
const repository = payload.repository?.full_name;
if (!repository) {
return new Response("Missing repository", { status: 400 });
}
const agentName = repository.toLowerCase().replace(/\//g, "-");
const agent = await getAgentByName(env.RepoAgent, agentName);
return agent.fetch(request);
}
},
};export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
if (request.method === "POST" && url.pathname === "/webhooks/github") {
const rawBody = await request.clone().text();
const signature = request.headers.get("X-Hub-Signature-256");
if (
!(await verifyGitHubWebhook(
rawBody,
signature,
env.WEBHOOK_SECRET,
))
) {
return new Response("Invalid signature", { status: 401 });
}
let payload: { repository?: { full_name?: string } };
try {
payload = JSON.parse(rawBody);
} catch {
return new Response("Invalid payload", { status: 400 });
}
const repository = payload.repository?.full_name;
if (!repository) {
return new Response("Missing repository", { status: 400 });
}
const agentName = repository.toLowerCase().replace(/\//g, "-");
const agent = await getAgentByName(env.RepoAgent, agentName);
return agent.fetch(request);
}
},
} satisfies ExportedHandler<Env>;Ověřte ID entit v URL adresách
Podpis těla u poskytovatele neověřuje adresu URL webhooku. Pokud adresa URL obsahuje ID entity, porovnejte ho s odpovídající identitou z ověřeného payloadu poskytovatele a případnou neshodu odmítněte ještě před voláním getAgentByName().
Odvoďte identitu Slack z ověřeného těla požadavku
Slack neposílá ověřený X-Slack-Team-Id hlavičku směrování. Ověřte časově opatřený podpis Slacku a okno pro opakované přehrání proti syrovému tělu požadavku, poté přečtěte team_id z ověřené události nebo těla formuláře.
Ověření podpisu
Podpisy webhooků vždy ověřte, než jim budete důvěřovat nebo je zpracovávat.
Vzor GitHub HMAC-SHA256
Rychlého startu verifyGitHubWebhook() pomocná funkce ověřuje GitHubův sha256=<hex> podpis nad nezpracovaným tělem požadavku pomocí crypto.subtle.verify(). Tento formát je specifický pro GitHub. Ostatní poskytovatelé používají jiné kódování podpisů, podepsané vstupy, kontroly časového razítka i ochranu proti replay útokům. Postupujte podle dokumentace poskytovatele, na kterou odkazuje Běžní poskytovatelé webhooků.
Hlavičky specifické pro poskytovatele
| Poskytovatel | Signature Header | Algoritmus |
|---|---|---|
| GitHub | X-Hub-Signature-256 |
HMAC-SHA256 |
| Stripe | Stripe-Signature |
HMAC-SHA256 (s časovým razítkem) |
| Twilio | X-Twilio-Signature |
HMAC-SHA1 |
| Slack | X-Slack-Signature |
HMAC-SHA256 (s časovým razítkem) |
| Shopify | X-Shopify-Hmac-Sha256 |
HMAC-SHA256 (base64) |
Zpracování webhooků
Handler onRequest
Použijte onRequest() ke zpracování příchozích webhooků ve vašem agentovi. Pokud Worker požadavek ještě neověřil, ověřte ho před parsováním těla. Tento příklad znovu využívá z úvodní příručky verifyGitHubWebhook() pomocná funkce:
export class WebhookAgent extends Agent {
async onRequest(request) {
// 1. Validate method
if (request.method !== "POST") {
return new Response("Method not allowed", { status: 405 });
}
// 2. Get the GitHub event type
const eventType = request.headers.get("X-GitHub-Event") ?? "unknown";
// 3. Verify the GitHub signature
const signature = request.headers.get("X-Hub-Signature-256");
const body = await request.text();
if (
!(await verifyGitHubWebhook(body, signature, this.env.WEBHOOK_SECRET))
) {
return new Response("Invalid signature", { status: 401 });
}
// 4. Parse and process
const payload = JSON.parse(body);
await this.handleEvent(eventType, payload);
// 5. Respond quickly
return new Response("OK", { status: 200 });
}
async handleEvent(type, payload) {
// Update state (broadcasts to connected clients)
this.setState({
...this.state,
lastEventType: type,
lastEventTime: new Date().toISOString(),
});
// Store in SQL for history
this
.sql`INSERT INTO events (type, payload, timestamp) VALUES (${type}, ${JSON.stringify(payload)}, ${Date.now()})`;
}
}export class WebhookAgent extends Agent {
async onRequest(request: Request): Promise<Response> {
// 1. Validate method
if (request.method !== "POST") {
return new Response("Method not allowed", { status: 405 });
}
// 2. Get the GitHub event type
const eventType = request.headers.get("X-GitHub-Event") ?? "unknown";
// 3. Verify the GitHub signature
const signature = request.headers.get("X-Hub-Signature-256");
const body = await request.text();
if (
!(await verifyGitHubWebhook(body, signature, this.env.WEBHOOK_SECRET))
) {
return new Response("Invalid signature", { status: 401 });
}
// 4. Parse and process
const payload = JSON.parse(body);
await this.handleEvent(eventType, payload);
// 5. Respond quickly
return new Response("OK", { status: 200 });
}
private async handleEvent(type: string, payload: unknown) {
// Update state (broadcasts to connected clients)
this.setState({
...this.state,
lastEventType: type,
lastEventTime: new Date().toISOString(),
});
// Store in SQL for history
this
.sql`INSERT INTO events (type, payload, timestamp) VALUES (${type}, ${JSON.stringify(payload)}, ${Date.now()})`;
}
}Ukládání webhook událostí
Pomocí SQLite ukládejte události webhooků pro historii a přehrávání.
Schéma tabulky událostí
class WebhookAgent extends Agent {
async onStart() {
this.sql`
CREATE TABLE IF NOT EXISTS events (
id TEXT PRIMARY KEY,
type TEXT NOT NULL,
action TEXT,
title TEXT NOT NULL,
description TEXT,
url TEXT,
actor TEXT,
payload TEXT,
timestamp TEXT NOT NULL
)
`;
this.sql`
CREATE INDEX IF NOT EXISTS idx_events_timestamp
ON events(timestamp DESC)
`;
}
}class WebhookAgent extends Agent {
async onStart(): Promise<void> {
this.sql`
CREATE TABLE IF NOT EXISTS events (
id TEXT PRIMARY KEY,
type TEXT NOT NULL,
action TEXT,
title TEXT NOT NULL,
description TEXT,
url TEXT,
actor TEXT,
payload TEXT,
timestamp TEXT NOT NULL
)
`;
this.sql`
CREATE INDEX IF NOT EXISTS idx_events_timestamp
ON events(timestamp DESC)
`;
}
}Vyčistěte staré události
Zabránit neomezenému růstu ponecháním pouze nedávných událostí:
// Keep last 100 events
this.sql`
DELETE FROM events WHERE id NOT IN (
SELECT id FROM events ORDER BY timestamp DESC LIMIT 100
)
`;
// Or delete events older than 30 days
this.sql`
DELETE FROM events
WHERE timestamp < datetime('now', '-30 days')
`;// Keep last 100 events
this.sql`
DELETE FROM events WHERE id NOT IN (
SELECT id FROM events ORDER BY timestamp DESC LIMIT 100
)
`;
// Or delete events older than 30 days
this.sql`
DELETE FROM events
WHERE timestamp < datetime('now', '-30 days')
`;Dotazování na události
import { Agent, callable } from "agents";
class WebhookAgent extends Agent {
@callable()
getEvents(limit = 20) {
return [
...this.sql`
SELECT * FROM events
ORDER BY timestamp DESC
LIMIT ${limit}
`,
];
}
@callable()
getEventsByType(type, limit = 20) {
return [
...this.sql`
SELECT * FROM events
WHERE type = ${type}
ORDER BY timestamp DESC
LIMIT ${limit}
`,
];
}
}import { Agent, callable } from "agents";
class WebhookAgent extends Agent {
@callable()
getEvents(limit = 20) {
return [
...this.sql`
SELECT * FROM events
ORDER BY timestamp DESC
LIMIT ${limit}
`,
];
}
@callable()
getEventsByType(type: string, limit = 20) {
return [
...this.sql`
SELECT * FROM events
WHERE type = ${type}
ORDER BY timestamp DESC
LIMIT ${limit}
`,
];
}
}Rozesílání v reálném čase
Když dorazí webhook, aktualizujte stav agenta, aby se automaticky odeslal připojeným klientům WebSocket.
class WebhookAgent extends Agent {
async processWebhook(eventType, payload) {
// Update state - this automatically broadcasts to all connected clients
this.setState({
...this.state,
stats: payload.stats,
lastEvent: {
type: eventType,
timestamp: new Date().toISOString(),
},
});
}
}class WebhookAgent extends Agent {
private async processWebhook(eventType: string, payload: WebhookPayload) {
// Update state - this automatically broadcasts to all connected clients
this.setState({
...this.state,
stats: payload.stats,
lastEvent: {
type: eventType,
timestamp: new Date().toISOString(),
},
});
}
}Na straně klienta:
import { useAgent } from "agents/react";
function Dashboard() {
const [state, setState] = useState(null);
const agent = useAgent({
agent: "webhook-agent",
name: "my-entity-id",
onStateUpdate: (newState) => {
setState(newState); // Automatically updates when webhooks arrive
},
});
return <div>Last event: {state?.lastEvent?.type}</div>;
}Vzory
Deduplikace událostí
Prevence zpracování duplicitních událostí pomocí ID událostí:
class WebhookAgent extends Agent {
async handleEvent(eventId, payload) {
// Check if already processed
const existing = [
...this.sql`
SELECT id FROM events WHERE id = ${eventId}
`,
];
if (existing.length > 0) {
console.log(`Event ${eventId} already processed, skipping`);
return;
}
// Process and store
await this.processPayload(payload);
this.sql`INSERT INTO events (id, ...) VALUES (${eventId}, ...)`;
}
}class WebhookAgent extends Agent {
async handleEvent(eventId: string, payload: unknown) {
// Check if already processed
const existing = [
...this.sql`
SELECT id FROM events WHERE id = ${eventId}
`,
];
if (existing.length > 0) {
console.log(`Event ${eventId} already processed, skipping`);
return;
}
// Process and store
await this.processPayload(payload);
this.sql`INSERT INTO events (id, ...) VALUES (${eventId}, ...)`;
}
}Reagovat rychle, zpracovávat asynchronně
Poskytovatelé webhooků očekávají rychlé odpovědi. Pro náročné zpracování použijte frontu:
class WebhookAgent extends Agent {
async onRequest(request) {
const payload = await request.json();
// Quick validation
if (!this.isValid(payload)) {
return new Response("Invalid", { status: 400 });
}
// Queue heavy processing
await this.queue("processWebhook", payload);
// Respond immediately
return new Response("Accepted", { status: 202 });
}
async processWebhook(payload) {
// Heavy processing happens here, after response sent
await this.enrichData(payload);
await this.notifyDownstream(payload);
await this.updateAnalytics(payload);
}
}class WebhookAgent extends Agent {
async onRequest(request: Request): Promise<Response> {
const payload = await request.json();
// Quick validation
if (!this.isValid(payload)) {
return new Response("Invalid", { status: 400 });
}
// Queue heavy processing
await this.queue("processWebhook", payload);
// Respond immediately
return new Response("Accepted", { status: 202 });
}
async processWebhook(payload: WebhookPayload) {
// Heavy processing happens here, after response sent
await this.enrichData(payload);
await this.notifyDownstream(payload);
await this.updateAnalytics(payload);
}
}Pokud je asynchronní úloha jediné kolo chatu Think, použijte submitMessages() místo toho. Okamžitě vrátí trvalé ID odeslání a umožní, aby opakování používala idempotentní klíč, místo aby duplikovala kolo zprávy:
const submission = await this.submitMessages(messages, {
idempotencyKey: payload.id,
});
return Response.json(
{ submissionId: submission.submissionId },
{ status: 202 },
);const submission = await this.submitMessages(messages, {
idempotencyKey: payload.id,
});
return Response.json(
{ submissionId: submission.submissionId },
{ status: 202 },
);Pokud webhook zajišťuje vedlejší efekty aplikace kolem daného kola, například obnovení vlákna u poskytovatele a odeslání viditelné odpovědi, použijte startFiber() kolem dané úlohy. Vlákna managed fiber uchovávají stav, deduplikují opakování ze strany poskytovatele a umožňují onFiberRecovered() nebo resolveFiber() zaznamenává výsledek obnovení na úrovni aplikace.
Směrování mezi více poskytovateli
Použijte jeden typovaný helper pro ověřování a parsování specifické pro daného poskytovatele. Musí ověřit nezpracovaný požadavek podle dokumentace poskytovatele, na kterou odkazuje Běžní poskytovatelé webhooků, poté odvoďte agentName pouze z ověřeného těla.
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (request.method === "POST" && url.pathname.startsWith("/webhooks/")) {
const verified = await verifyAndParseWebhook(request.clone(), env);
if (!verified) {
return new Response("Invalid signature", { status: 401 });
}
switch (verified.provider) {
case "github":
return (
await getAgentByName(env.GitHubAgent, verified.agentName)
).fetch(request);
case "stripe":
return (
await getAgentByName(env.StripeAgent, verified.agentName)
).fetch(request);
case "slack":
return (
await getAgentByName(env.SlackAgent, verified.agentName)
).fetch(request);
}
}
return (
(await routeAgentRequest(request, env)) ??
new Response("Not found", { status: 404 })
);
},
};type VerifiedWebhook =
| { provider: "github"; agentName: string }
| { provider: "stripe"; agentName: string }
| { provider: "slack"; agentName: string };
declare function verifyAndParseWebhook(
request: Request,
env: Env,
): Promise<VerifiedWebhook | null>;
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
if (request.method === "POST" && url.pathname.startsWith("/webhooks/")) {
const verified = await verifyAndParseWebhook(request.clone(), env);
if (!verified) {
return new Response("Invalid signature", { status: 401 });
}
switch (verified.provider) {
case "github":
return (
await getAgentByName(env.GitHubAgent, verified.agentName)
).fetch(request);
case "stripe":
return (
await getAgentByName(env.StripeAgent, verified.agentName)
).fetch(request);
case "slack":
return (
await getAgentByName(env.SlackAgent, verified.agentName)
).fetch(request);
}
}
return (
(await routeAgentRequest(request, env)) ??
new Response("Not found", { status: 404 })
);
},
} satisfies ExportedHandler<Env>;Odesílání odchozích webhooků
Agents mohou také odesílat webhooky externím službám:
export class NotificationAgent extends Agent {
async notifySlack(message) {
const response = await fetch(this.env.SLACK_WEBHOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: message }),
});
if (!response.ok) {
throw new Error(`Slack notification failed: ${response.status}`);
}
}
async sendSignedWebhook(url, payload) {
const body = JSON.stringify(payload);
const signature = await this.sign(body, this.env.WEBHOOK_SECRET);
await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Signature": signature,
},
body,
});
}
}export class NotificationAgent extends Agent {
async notifySlack(message: string) {
const response = await fetch(this.env.SLACK_WEBHOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: message }),
});
if (!response.ok) {
throw new Error(`Slack notification failed: ${response.status}`);
}
}
async sendSignedWebhook(url: string, payload: unknown) {
const body = JSON.stringify(payload);
const signature = await this.sign(body, this.env.WEBHOOK_SECRET);
await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Signature": signature,
},
body,
});
}
}Osvědčené postupy zabezpečení
- Vždy ověřujte podpisy - Nikdy nedůvěřujte neověřeným webhookům.
- Použití secrets prostředí - Ukládejte tajné klíče pomocí
wrangler secret put, nikoli v kódu. - Reagovat rychle - Vraťte 200/202 do několika sekund, abyste se vyhnuli opakování.
- Ověřujte payloady - Před zpracováním zkontrolujte povinná pole.
- Protokolujte odmítnutí - Sledujte neplatné podpisy pro účely bezpečnostního monitoringu.
- Použití HTTPS - Adresy URL webhooků by měly vždy používat TLS.
// Store secrets securely
// wrangler secret put GITHUB_WEBHOOK_SECRET
// Access in agent
const secret = this.env.GITHUB_WEBHOOK_SECRET;// Store secrets securely
// wrangler secret put GITHUB_WEBHOOK_SECRET
// Access in agent
const secret = this.env.GITHUB_WEBHOOK_SECRET;Běžní poskytovatelé webhooků
| Poskytovatel | Dokumentace |
|---|---|
| GitHub | Události a payloady webhooků ↗ |
| Stripe | Podpisy webhooků ↗ |
| Twilio | Ověřujte požadavky webhooků ↗ |
| Slack | Ověřování požadavků ↗ |
| Shopify | Ověřování webhooků ↗ |
| SendGrid | Webhook událostí ↗ |
| Linear | Webhooks ↗ |