Scrydon
ExtensionsAuthoringModel families

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 methodRequiredPlatform operationPurpose
verifyyesextensions.webhooks.verifyAuthenticate the raw request before processing
transformyesextensions.webhooks.transformConvert the vendor body into the workflow event
challengeHandlernoextensions.webhooks.challengeAnswer provider verification handshakes
matchEventnoextensions.webhooks.matchDecide whether an event matches a configured trigger
extractIdempotencyKeynoextensions.webhooks.idempotency.extractDerive a stable delivery identity for retry deduplication
subscription.subscribenoextensions.webhooks.subscriptions.createCreate a vendor subscription
subscription.renewnoextensions.webhooks.subscriptions.renewRenew an expiring vendor subscription
subscription.unsubscribenoextensions.webhooks.subscriptions.deleteDelete a vendor subscription
polling.initializenoextensions.webhooks.polling.initializeEstablish the first polling cursor
polling.pollnoextensions.webhooks.pollFetch events when push delivery is unavailable
buildTestPayloadnoextensions.webhooks.test.buildBuild a provider-aware test event
responseFormatternoextensions.webhooks.response.formatFormat 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.expiresAt is a Date in 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.
  • transform may return null to ignore an event deliberately.
  • Polling returns payloads, the next changes cursor, and apiCallCount; keep the cursor JSON-serializable because it is persisted between runs.
On this page

On this page