SextantDocs
v2.4latest
v2.4latestv1.9legacy

Switching keeps you on the same page when it exists in the other version.

On this page

WebhooksNew

Receive job events at your HTTPS endpoint, verify the signature on every delivery, and handle retries and duplicates.

Updated Guides Version v2.4

Webhooks push events to a URL you control, so you do not have to poll GET /v2/jobs.

Events

job.succeededevent
An attempt returned without error.
job.failedevent
An attempt failed and another is planned.
job.deadevent
The last attempt failed. A human should look.
job.cancelledevent
The job was cancelled.
pingevent
Sent by the test endpoint and when you create a webhook.

Register an endpoint

Shell
sextant webhooks create https://hooks.example.com/sextant --events job.failed,job.dead

The response prints the signing secret once, as whsec_.... Store it as an environment variable in the service that receives the events.

The delivery

Sextant sends a POST with a JSON body and these headers:

HTTP
POST /sextant HTTP/1.1
Content-Type: application/json
Sextant-Event: job.dead
Sextant-Delivery: dlv_91b0c2f4
Sextant-Signature: t=1760951778,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
JSON
{
  "id": "evt_4c1e0a",
  "type": "job.dead",
  "created_at": "2026-10-20T09:16:18Z",
  "data": { "job": { "id": "job_8f3k2m9q", "queue": "emails", "state": "dead", "attempt": 5 } }
}

Verify the signature

The signature is an HMAC-SHA256 of {t}.{raw body} with your secret. Compute it over the raw bytes of the body, before any JSON parsing, and compare in constant time. Reject timestamps older than five minutes.

NodeTypeScript
import { Sextant } from '@sextant/node';

export async function POST(req: Request) {
  const body = await req.text();
  const event = Sextant.webhooks.verify(body, req.headers.get('sextant-signature')!, process.env.SEXTANT_WEBHOOK_SECRET!);
  if (event.type === 'job.dead') await pager.notify(event.data.job.id);
  return new Response(null, { status: 204 });
}
PythonPython
from sextant import Sextant, SignatureError

def webhook(request):
    try:
        event = Sextant.webhooks.verify(
            request.body, request.headers["Sextant-Signature"], os.environ["SEXTANT_WEBHOOK_SECRET"]
        )
    except SignatureError:
        return HttpResponse(status=400)
    if event["type"] == "job.dead":
        pager.notify(event["data"]["job"]["id"])
    return HttpResponse(status=204)
GoGo
func handler(w http.ResponseWriter, r *http.Request) {
	body, _ := io.ReadAll(r.Body)
	event, err := sextant.VerifyWebhook(body, r.Header.Get("Sextant-Signature"), os.Getenv("SEXTANT_WEBHOOK_SECRET"))
	if err != nil {
		http.Error(w, "bad signature", http.StatusBadRequest)
		return
	}
	if event.Type == "job.dead" {
		pager.Notify(event.Data.Job.ID)
	}
	w.WriteHeader(http.StatusNoContent)
}

Retries and duplicates

Any response outside 2xx, or no response within 10 seconds, counts as a failed delivery. Sextant retries with growing waits for 24 hours: 10 s, 1 min, 5 min, 30 min, then every 2 hours. After 24 hours the delivery is dropped and the webhook is flagged in sextant webhooks list.

Deliveries are at least once. Use Sextant-Delivery or the event id to ignore ones you already handled, and reply quickly: do the real work after answering with 204.

Test an endpoint

Shell
sextant webhooks test whk_5d02ab

sends a signed ping and prints the status and duration of the attempt.