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.
Create a webhook
Section titled “Create a webhook”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.
What your endpoint receives
Section titled “What your endpoint receives”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>"> |
{ "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.
Verify the signature
Section titled “Verify the signature”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.
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.
Rotate the secret
Section titled “Rotate the secret”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.
Retries and the delivery log
Section titled “Retries and the delivery log”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.