Start work from webhooks
Verify incoming events and publish queue jobs for the matching workflow.
Receive a webhook and publish a job
Section titled “Receive a webhook and publish a job”Use a webhook source to verify the incoming request, then route the accepted event to a queue job. serveTriggers() handles the HTTP request; workers execute the workflow after publication.
Adding the outpost:fix label to an issue or a pull request publishes a fix job to the queue. A worker runs it with defineWorkflowJob(): see Job queues and workers.
A job names a registered worker handler, a runId of at most 256 characters and an optional JSON input. Keep on() fast: GitHub waits 10 seconds for an answer, Slack 3 seconds.
Pick a source
Section titled “Pick a source”| Source | Verification | Delivery identifier | event.actor |
|---|---|---|---|
createGithubWebhook({ secret }) | X-Hub-Signature-256, an HMAC of the body. JSON or form payloads. | X-GitHub-Delivery | github:<login> |
createGitlabWebhook({ signingToken }) | webhook-signature with a whsec_ signing token (GitLab 19.0+), 5-minute window. | webhook-id | gitlab:<username> |
createGitlabWebhook({ token }) | X-Gitlab-Token equals the token. The body is not signed. | Idempotency-Key, else X-Gitlab-Event-UUID | gitlab:<username> |
createSlackSource({ signingSecret }) | X-Slack-Signature over the timestamp and body, 5-minute window. | trigger_id | slack:<user id> |
createStandardWebhook({ secret }) | Standard Webhooks whsec_ secret, 5-minute window. | webhook-id | none |
Prefer a GitLab signing token: a plain token travels as is in a header and does not sign the body. toleranceMs changes the 5-minute window. Slack sources accept slash commands and interactive payloads.
Read the event
Section titled “Read the event”Two helpers recognize the common events and return undefined for everything else.
API reference: labelAdded, commandIssued and TriggerEvent.
Use the event contract when handling another kind of delivery.
API reference: TriggerEvent.
Authorize senders
Section titled “Authorize senders”A verified signature proves the request comes from your GitHub, GitLab or Slack integration, not that its author may start a workflow. Anyone who can comment on a public repository can write /outpost: check event.actor against an explicit list.
Pass fromCommand as a route’s on. event.actor is not an Outpost gate actor: approvals authenticate their deciders separately.
Read the HTTP response
Section titled “Read the HTTP response”| Status | Meaning |
|---|---|
202 | Job published, or already published for this delivery. The body is {"job": "<id>"}. |
204 | Verified event ignored: on() returned undefined. |
400 | The request body could not be read. |
401 | Verification failed: signature, secret, timestamp window or a missing header. |
404 | No route for this path. |
405 | A method other than POST. |
413 | Body over maxBytes: 1 MiB by default, 25 MiB at most. |
500 | on() threw or returned an invalid job. |
503 | The queue rejected the job. The sender can retry the same delivery. |
Slack routes answer 200 with an empty body instead of 202 and 204. onError receives the failure’s path, stage (verify, route or enqueue) and delivery, never a secret.
Deduplicate redeliveries
Section titled “Deduplicate redeliveries”The job identifier contains the delivery identifier. A sender retry or a manual redelivery reuses it, so the queue keeps a single job for as long as it retains that job.
A new delivery publishes a new job, even for the same event, such as a label added again. The runId decides whether it does work twice. Handlers that post results still need their own idempotency keys.
Derive the run from the payload
Section titled “Derive the run from the payload”Build runId from what identifies the work in the payload: owner/name#12, or a head commit. Jobs with the same runId and the same input share one checkpoint: defineWorkflowJob() restores the tasks already done instead of running them again.
A different input under the same runId fails with an incompatible checkpoint, because the checkpoint version includes a digest of the input. Two commands with different text on one issue therefore need distinct run IDs, for example with event.delivery added.
GitHub signatures carry no timestamp, so a captured request can be replayed under a new delivery identifier. A payload-derived runId makes that replay converge on the same run. The Review a pull request on demand recipe keys each run on the head commit.
Rotate a secret
Section titled “Rotate a secret”Every secret option also accepts a callback that returns the secrets accepted right now. The source calls it on each request.
Accept both secrets, change the secret at the sender, then drop the old one. A callback that throws or returns no secret rejects every request with 401.
Operate the server
Section titled “Operate the server”serveTriggers() listens on 127.0.0.1 by default; host and port change it. Put a reverse proxy that terminates TLS in front of it, and expose only the route paths.
Close the server first, so no request reaches a closed queue.
Limits
Section titled “Limits”- Outpost does not call the GitHub, GitLab or Slack APIs: your workflow posts comments or messages about the result.
- The Slack Events API and its URL verification challenge are not supported.
- No
outpostCLI command runs the server: start it from your own script.
API: serveTriggers · createGithubWebhook · createGitlabWebhook · createSlackSource · createStandardWebhook · labelAdded · commandIssued · TriggerEvent · TriggerJob · defineWorkflowJob.