INTEGRITY Dokumentace

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í

  1. Vždy ověřujte podpisy - Nikdy nedůvěřujte neověřeným webhookům.
  2. Použití secrets prostředí - Ukládejte tajné klíče pomocí wrangler secret put, nikoli v kódu.
  3. Reagovat rychle - Vraťte 200/202 do několika sekund, abyste se vyhnuli opakování.
  4. Ověřujte payloady - Před zpracováním zkontrolujte povinná pole.
  5. Protokolujte odmítnutí - Sledujte neplatné podpisy pro účely bezpečnostního monitoringu.
  6. 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 ↗

Další kroky

Zařazování úloh do fronty

Zpracování úloh na pozadí.

Směrování e-mailů

Zpracovávejte příchozí e-maily ve svém agentovi.

Agents API

Kompletní referenční dokumentace API pro Agents SDK.