Webhook
Verify, transform, subscribe, poll, test, and format vendor webhooks through one capability.
Use the webhook capability for vendors that send events into the platform. Every implementation verifies and transforms incoming events. Optional methods cover challenge handshakes, trigger matching, idempotency, subscription lifecycle, polling fallbacks, test payloads, and provider-specific responses.
Define the capability
import { defineCapabilityWebhook } from "@scrydon/sdk-authoring/extensions/authoring/define";
const webhookCapability = defineCapabilityWebhook({
async verify(request) {
const signature = request.headers["x-webhook-signature"];
const valid = verifyHmac(request.rawBody, signature, request.secret);
return { valid, error: valid ? undefined : "Invalid signature" };
},
async transform(body, context) {
const event = body as { id: string; type: string; data: unknown };
return { id: event.id, eventType: event.type, data: event.data };
},
async challengeHandler(request) {
const token = new URL(request.url).searchParams.get("challenge");
return token ? new Response(token, { status: 200 }) : null;
},
matchEvent(event, trigger) {
return event.eventType === trigger.config.eventType;
},
subscription: {
async subscribe(request) {
return createVendorSubscription(request);
},
async renew(subscriptionId, request) {
return renewVendorSubscription(subscriptionId, request);
},
async unsubscribe(subscriptionId, credentials) {
await deleteVendorSubscription(subscriptionId, credentials.accessToken);
},
},
extractIdempotencyKey(headers) {
return headers["x-webhook-delivery-id"] ?? null;
},
async buildTestPayload(request) {
return {
status: 200,
payload: { type: "webhook.test", webhookId: request.webhookId },
};
},
polling: {
async initialize(request) {
return { cursor: { since: new Date().toISOString() } };
},
async poll(request) {
return pollVendorEvents(request.credentials.accessToken, request.cursor);
},
},
responseFormatter: {
formatSuccess() {
return new Response(null, { status: 202 });
},
formatError(error) {
return Response.json({ error: error.message }, { status: 400 });
},
},
});subscribe, renew, polling, and test helpers receive credentials resolved for the
current tenant inside the platform execution boundary. Never persist, log, or return
those credentials. verify receives the configured webhook secret separately so an
incoming request cannot supply the value used to authenticate itself.
Complete method surface
The platform maps each authoring method to a closed, typed operation. Application code should use the capability instead of loading code releases or calling vendor helpers directly.
| Authoring method | Required | Platform operation | Purpose |
|---|---|---|---|
verify | yes | extensions.webhooks.verify | Authenticate the raw request before processing |
transform | yes | extensions.webhooks.transform | Convert the vendor body into the workflow event |
challengeHandler | no | extensions.webhooks.challenge | Answer provider verification handshakes |
matchEvent | no | extensions.webhooks.match | Decide whether an event matches a configured trigger |
extractIdempotencyKey | no | extensions.webhooks.idempotency.extract | Derive a stable delivery identity for retry deduplication |
subscription.subscribe | no | extensions.webhooks.subscriptions.create | Create a vendor subscription |
subscription.renew | no | extensions.webhooks.subscriptions.renew | Renew an expiring vendor subscription |
subscription.unsubscribe | no | extensions.webhooks.subscriptions.delete | Delete a vendor subscription |
polling.initialize | no | extensions.webhooks.polling.initialize | Establish the first polling cursor |
polling.poll | no | extensions.webhooks.poll | Fetch events when push delivery is unavailable |
buildTestPayload | no | extensions.webhooks.test.build | Build a provider-aware test event |
responseFormatter | no | extensions.webhooks.response.format | Format success or error acknowledgements |
Optional methods return an explicit not_supported result at the platform boundary
when they are absent. Provider failures remain failures; they are not treated as an
unsupported capability or an implicit success.
Runtime values
WebhookSubscription.expiresAtis aDatein authoring code. The worker boundary serializes it to an ISO-8601 timestamp for platform callers.- Challenge and response formatter methods may return a standard
Response. Status, headers, and body are serialized across the worker boundary. transformmay returnnullto ignore an event deliberately.- Polling returns
payloads, the nextchangescursor, andapiCallCount; keep the cursor JSON-serializable because it is persisted between runs.