Skip to content

Outgoing webhooks

Outgoing webhooks send a signed HTTP request to your endpoint when something happens in your Orbit site, such as a work item being created. This page is for site administrators who set webhooks up and for developers who write the receiving endpoint. In development

Only site administrators manage webhooks. The routes need a signed-in session, because API tokens never carry site administration.

A webhook needs a name, an address and the events it listens to.

Field Rules
name 1 to 200 characters
url http or https, without credentials. An address that is not public is refused at once with invalid_webhook_url, and the host name is checked again on every connection.
events Event names such as issue.created, issue.updated, issue.deleted, comment.*, worklog.*, attachment.created, attachment.deleted, issuelink.created, issuelink.deleted, project.*, sprint.* and version.*. Use <entity>.* for every event of one kind, or * for all.
projectIds Up to 100 projects. Leave it empty for all projects.
jql Up to 2,000 characters. Limits work item, comment, work log, attachment and link events to work that matches. A deleted work item can no longer match, so its event is sent.
enabled Turn the webhook on or off. Turning it off cancels waiting deliveries.

A site can have 100 webhooks. The create request is POST /admin/webhooks on https://acme.orbit.inortix.com/api/v1. The answer includes the secret, which starts with owhk_, once. Orbit never shows it again.

A webhook acts as the administrator who last saved it. It sends only events of projects and work items that person may read, including work item security. It turns itself off, with a disabledReason, if that person stops administering the site or if the JQL no longer compiles for them. Saving the webhook again makes you its owner.

Each event is one POST with Content-Type: application/json.

Header Meaning
User-Agent Orbit-Webhooks/1
Orbit-Event The event name
Orbit-Event-Id The same on every attempt and redelivery. Use it to drop duplicates.
Orbit-Delivery The delivery
Orbit-Webhook The webhook
Orbit-Signature t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">
Example body (made-up values)
{
"id": "evt-8841",
"type": "issue.created",
"occurredAt": "2026-11-02T09:14:07Z",
"webhook": { "id": "wh-3", "name": "Build bot" },
"actor": { "id": "user-6", "name": "Alex" },
"project": { "id": "project-1", "key": "ORB", "name": "Orbit" },
"data": { "id": "issue-12", "key": "ORB-12" }
}

data is the work item as it was saved, or the comment, work log, attachment, link, project, sprint or version. Updates add changes. Comment, work log, attachment and link events add issue with id, key and summary, and {id, restricted: true} when the work item is restricted. A deleted object is only {id}. A body over 256 KiB keeps only data.id and data.key and sets truncated: true. Events committed before the webhook existed are not sent.

Compute the HMAC-SHA256 of <t>. followed by the raw body with your secret, and compare it with each v1 value. Reject old t values.

Python
import hashlib, hmac, time
def is_valid(secret: str, header: str, raw_body: bytes, max_age_s: int = 300) -> bool:
parts = [p.split("=", 1) for p in header.split(",")]
t = next(v for k, v in parts if k == "t")
candidates = [v for k, v in parts if k == "v1"]
if abs(time.time() - int(t)) > max_age_s:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return any(hmac.compare_digest(expected, c) for c in candidates)

The max_age_s value is your choice.

POST /admin/webhooks/{id}/secret returns a new secret, shown once. The body takes overlapHours from 0 to 168 (the default is 24). Until previousSecretValidUntil, every delivery carries two v1 values, one for each secret, so you can switch your endpoint without a gap.

Any 2xx answer is a success. Anything else, a timeout of 10 seconds or a lost connection counts as a failure. Orbit then retries after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 12 hours, which makes 7 tries. Redirects are not followed.

A webhook turns itself off, cancels what waits and notifies the site administrators in three cases. Its address resolves to one that Orbit refuses, your endpoint answers 410, or 20 deliveries in a row failed for good. Deliveries can arrive out of order.

Route Result
GET /admin/webhooks/{id}/deliveries The latest 100 deliveries, filtered by state: queued, sent, failed or cancelled
GET /admin/webhooks/{id}/deliveries/{delivery} Adds the payload and the first 1,000 characters of your answer
POST /admin/webhooks/{id}/deliveries/{delivery}/redeliver Queues the same payload again under the same event id
POST /admin/webhooks/{id}/test Sends a webhook.test delivery. Answers 409 webhook_disabled while the webhook is off.

Deliveries are kept for 30 days. Creating, changing, rotating, turning off and deleting a webhook are written to the audit log with the host of the address only.