definition and lets you run them in fixtures or live mode. Set definition on the registry entry and the surface shows a card per webhook and schedule, plus an OAuth declaration card and the Local OAuth panel. Without definition, the surface explains what to add.
Setup
asHarnessDefinition() erases the storage generic so the definition can sit in the heterogeneous registry array. The mock context is built from the definition’s own storage schemas and validates every read and write against them at runtime.
Webhooks
Each declared webhook gets a card, titled with itsid, with a payload editor seeded from examplePayload and a Deliver webhook button:
- Edit the JSON payload in the editor
- Click Deliver webhook
- The payload is validated against
payloadSchema; an invalid one never reaches the handler - Your real handler runs against the mock (fixtures) or live context, and the result appears below the card
Signature verification is logged as skipped — it is the transport’s job in
every environment. A webhook that declares
signature.scheme shows it as a
badge on its card (algorithm, signed payload, keyed pairs, timestamp source
and replay window), so you can read back exactly what the host ingress will
compute. See Webhooks.Schedules
Each declared schedule gets a card, titled with itsid and showing its cron, with a Run now button:
trigger: 'manual'; the host scheduler uses trigger: 'schedule', so your handler must be safe under both.
OAuth
A declaredoauth gets an OAuth declaration card showing:
- Authorization URL and token URL
- Declared scopes
- PKCE (yes/no)
- The client id and client secret secret names
- Egress coverage: one badge per endpoint origin, marked as failing if your
egressdoes not cover it
- Creating an app in your vendor’s console and adding the redirect URI (with a Copy button)
- Enabling the declared scopes
- Pasting the credentials and saving them
Fixtures mode
In fixtures mode, the harness answersoauth/status from the registry entry’s oauth.connected field, so an OAuth widget renders with zero traffic and you do not hand-write that fixture. It defaults to connected. Pass a boolean, or a per-variant map:
Live mode
In live mode, the shell runs the real authorize → callback →onTokens loop through your developer app credentials. See Live Mode.
triggerMocks
The mock context is built from your definition’s schemas and seeded fromtriggerMocks:
storage
Storage rows per scope, validated against your definition’sstorage schemas. Keep them fake: everything in harness.config.ts is compiled into the browser bundle.
secrets
Secrets by tier for the fixtures-mode mock context.account is writable through ctx.secrets.set(); admin stands in for admin-issued credentials and rejects writes. In live mode, admin secrets come from .ekanos/local-secrets.json instead.
triggerMocks.secrets, because those literals are compiled into the browser bundle and the .next build output.
fetchHandlers
Only for responses that must be dynamic or stateful. Yourfixtures already answer ctx.fetch (see Fixtures), so reach for handlers only when you need something that changes.
A string match matches URLs that start with it; a RegExp is tested against the full URL. Handlers are tried before fixtures and win for the URLs they match. A handler call that nothing answers gets the same NoRecordedResponseError a widget gets, rather than createMockContext()’s default empty 200.
onActivate
TheonActivate hook runs on the Activation surface, not the Triggers surface. Submitting the activation form there runs your real hook (if declared) against the context the toolbar mode selects—browser mock in fixtures, live context in live—and shows the outcome (ran, skipped because not declared, or threw with the message) below the connect dialog.
Invocation results
When a webhook or schedule runs, the card shows a result panel: a summary badge (with detail, if any), the handler’s logs, and the account and user storage after the run. In fixtures mode every invocation runs in the browser against one mock context per visit, seeded fromtriggerMocks, so state accumulates across invocations; leaving the page resets it.
In live mode, the buttons post to /api/integrations/<slug>/trigger and handlers run on the server-held live context, which persists until the dev server restarts.
Troubleshooting
Webhook validation fails
Check your payload against thepayloadSchema. An invalid payload never reaches the handler.
Handler error: no recorded response
A handler’sctx.fetch call didn’t match a fixture. See Fixtures for how to record it. The error message shows the URL to paste (with credentials redacted).
Payload editor JSON is invalid
Check for trailing commas, missing quotes, or unescaped special characters.Triggers surface is empty
Your entry has nodefinition field. Add it with asHarnessDefinition(myDefinition).
Egress coverage badge is red
Youroauth.provider.authorizationUrl or tokenUrl origin is not in your definition’s egress. Add it or check for typos.