Event-Driven Orbs

Webhooks

An Amp plugin can create a public HTTP endpoint that stores events and wakes its orb. Use a webhook when an external service such as GitHub needs to start work in an orb. Unlike a portal, a webhook does not expose a server running inside the orb.

Webhooks work only in plugins running in Amp-managed orbs. Register the handler from the plugin’s entry point with a short, stable key:

const { url } = await amp.createWebhook({
	key: 'github-events',
	headers: ['x-hub-signature-256'],
	handler: async (event, ctx) => {
		await verifyAndApply(event.id, event.body, event.headers['x-hub-signature-256'], ctx.signal)
	},
})

The returned url accepts POST requests. Configure that URL in the external service. Ask Amp to create and load the plugin when you do not need to write it yourself. Run amp plugins show-docs inside the orb to read the Plugin API installed there.

Example

You can ask Amp in an orb thread to write, load, and wire up the webhook plugin for you:

Create a plugin that registers a webhook with the key `github-events` and captures the `x-hub-signature-256` header. When GitHub sends a push event for the default branch, pull the latest changes, run the test suite, and reply in this thread with the result. Verify the signature with a secret from a gitignored file before acting, and dedupe on the event ID. Load the plugin and give me the webhook URL and setup steps for GitHub.

Amp writes the plugin into .amp/plugins/, loads it, and replies with the webhook URL to paste into the external service.

Delivery Guarantees

For threads in a project, the registration belongs to the user, project, plugin, and key. A checked-in plugin therefore gets the same URL when it loads in another thread in that project. The thread that first registered the webhook remains its owning thread and handles its events. Loading the plugin in another thread still returns that URL when the owning thread is archived, but does not transfer ownership or resume delivery. For threads without a project, each thread gets a separate registration.

Registering the same key again keeps the same URL across plugin reloads and orb restarts. An incoming request is stored before Amp returns HTTP 202, then Amp wakes the owning orb and calls the handler. HTTP 202 means that Amp queued the event. It does not mean that the handler finished.

Delivery is at least once. Store event.id with the action that the handler performs so a retry cannot apply the same action twice. The handler has 30 seconds to finish. Pass ctx.signal to cancellable network calls and throw when a temporary failure should be retried. Keep longer work in a durable queue or start another thread.

A thrown handler defers only that event. Amp retries it after 5 seconds and doubles the wait on each rejection, up to 5 minutes, while later events keep being delivered. Events are therefore not delivered in strict order. An event that the handler keeps rejecting for 24 hours is dropped and logged in the orb’s Amp log. Do not throw to wait for an unrelated condition such as another thread finishing; store the event and return instead.

Rate Limits

Each endpoint accepts a burst of 10 new events and refills at 10 events per minute. Amp returns HTTP 429 when the endpoint exceeds that rate or has 100 events waiting. Rate limit responses include Retry-After. A request body can be at most 1 MB. Send a stable Idempotency-Key header when the sender retries a request. A retry for an event that is still waiting does not use more rate capacity.

Security

Treat the webhook URL as a password. Anyone with it can submit events without signing in to Amp. Do not commit it, put it in a thread message, or write it to normal logs. If you save it in the orb, use a gitignored file that only the owner can read. Archiving the owning thread pauses the orb and makes the URL return HTTP 404.

Amp does not verify signatures from the external service. Add each signature or authorization header to headers, store the matching secret in project settings, and verify the signature before acting. Amp discards request headers that the plugin did not list. Validate the body and requested headers as untrusted input, and never use webhook text as agent instructions without checking it first.

Trigger Settings

Use trigger settings to pause or delete a webhook. Pausing keeps its URL but rejects new events until you resume it. Deleting it revokes the URL and removes its queued events.

If delivery is paused because the owning thread is archived, trigger settings show the reason and link to that thread. Restore the owning thread to allow delivery again. If you also paused the trigger in settings, it stays paused until you resume it there.

Keeping Threads Active

Use these rules for a thread that owns CI, a webhook, or other long-running automation:

  • Keep the owning thread unarchived. Archiving pauses its orb and webhook delivery.
  • Remove workspace or personal guidance that tells Amp to archive the thread when work finishes.
  • For CLI execute mode, pass --no-archive-after-execute. For SDK execute mode, set noArchiveAfterExecute in TypeScript or no_archive_after_execute in Python.
  • Publish a plugin to the workspace when every member must install it. Workspace publication does not transfer an existing webhook to a different owner or thread.

Testing

You can test an endpoint without putting its URL in shell history:

read -rsp 'Webhook URL: ' AMP_WEBHOOK_URL && echo
curl --fail-with-body \
	-X POST \
	-H 'Content-Type: application/json' \
	-H 'Idempotency-Key: test-event-1' \
	--data '{"action":"test"}' \
	"$AMP_WEBHOOK_URL"
unset AMP_WEBHOOK_URL

For scheduled and recurring work, see Automations.