These guides are primarily for agent readers. They explain how agents use Sails. If you are a person, start at sails.app or Add Sails.

Start a goal from a webhook

A platform webhook is an HTTP endpoint on the Sails server. An external service POSTs JSON. Sails starts a background goal as you (the repository owner), not as an anonymous visitor.

Owner-only. Agents and write members cannot webhook add.

This is not a published *.api.sh handler. A site handler is fast bash on the public mount. A platform webhook is a multi-step agent run.

Done looks like

webhook add prints a URL. A POST with the right auth returns JSON like { "status": "accepted", "goal_id": "…" }. goal wait on that id finishes. webhook show NAME still shows the same URL after you change the prompt.

Worked example: CI failure → investigate

Job: your CI can POST JSON when a build fails. You want a goal in sites/app that reads the payload and suggests a fix.

webhook add ci-failure \
  --path sites/app \
  --prompt 'Investigate this CI failure and suggest a fix: '

--path scopes the goal the same way goal --path does. --verify defaults to bearer.

Save the printed bearer token (whk_…). It is shown once. Only a hash is stored.

The output also includes a URL:

https://your-server/sails/hooks/{uuid}

From CI (or your laptop):

curl -X POST "https://your-server/sails/hooks/{uuid}" \
  -H "Authorization: Bearer whk_..." \
  -H "Content-Type: application/json" \
  -d @event.json

Accepted response includes goal_id. Then:

goal wait GOAL_ID

Inspect later with runs list --kind goal or goal log GOAL_ID.

Prompt placeholders

PlaceholderReplaced with
``Full JSON body (pretty-printed)
``type field
``id field

Change the prompt, keep the URL

Do not webhook rm + webhook add to fix a prompt or secret. That mints a new URL and breaks the provider until you update it there.

webhook update ci-failure --prompt 'CI failed. Summarize and open a fix plan: '
webhook list
webhook show ci-failure

Stripe or Svix instead of bearer

Stripe signs with Stripe-Signature. Svix-backed senders (Resend, Clerk, …) use svix-id, svix-timestamp, svix-signature. Store the signing secret first — see Store a secret.

secret set "whsec_..."
# prints: ssec_…

webhook add stripe-failures \
  --path sites/app \
  --verify stripe \
  --secret ssec_… \
  --events invoice.payment_failed,payment_intent.payment_failed \
  --prompt 'Stripe event  (). Summarize what failed.

'

webhook show stripe-failures

Paste that URL into the provider dashboard. Content type: application/json.

--events filters payload type. Other events still return HTTP 200 with { "status": "ignored" } so the provider does not retry forever.

--verify accepts bearer, stripe, or svix.

If Resend’s create-webhook returns a new signing_secret, secret set that value, then webhook update NAME --secret ssec_new…. Do not delete the webhook to rotate the secret.

Optional check:

webhook check-secret stripe-failures

Platform agent mail is separate

Inbound {uuid}@inbox.sails.app uses POST /sails/mail/resend and starts email.received as the agent. Do not webhook add for that. See Mail from an agent.

You can webhook add for your Resend account (bounces, your own receiving domain) with --verify svix.

Flags

webhook add NAME --prompt TEXT --path PATH [--verify bearer|stripe|svix] [--secret ssec_...] [--events TYPE,...]
webhook update NAME [--prompt TEXT] [--events TYPE,...] [--secret ssec_...]
webhook set-secret NAME --secret ssec_...
webhook check-secret NAME
webhook list
webhook show NAME
webhook rm NAME
CommandWhat it does
webhook addCreate. Bearer token shown once when --verify bearer
webhook updateChange prompt, events, or secret without changing the URL
webhook set-secretAlias for webhook update --secret
webhook check-secretSigning-key fingerprint vs the provider whsec
webhook showURL, verify mode, prompt (not the bearer token)
webhook rmDelete. The URL dies. Last resort

Failure modes

What you seeWhat it meansWhat to do
repository owner access requiredOwner-onlyStop retrying. An owner runs webhook add, or Approves elevation in a Slack goal
Provider retries / 401Bad bearer or signing secretBearer: you must use the whk_… from create. Stripe/Svix: secret set the current whsec, then webhook update --secret
You lost the bearer tokenShown once; only a hash is storedOwner creates a new webhook and updates the caller URL — or treat it as gone
Provider still hits an old URLYou webhook rm + addwebhook update next time. Fix the URL at the provider now
{ "status": "ignored" }--events filtered this typeExpected. Add the type to --events if you want a goal
Fast 200 but no agent workYou POSTed a published *.api.shPlatform URL is /sails/hooks/{uuid}, not your site path
Agent inbox never firesYou pointed Resend at a platform webhook for agent mailAgent mail is not webhook add — see Mail from an agent

See Give an agent a job for goals that are not HTTP-triggered.