Pull API¶
The Pull API is Hookaido's consumer protocol. Internal workers connect to the Pull API to dequeue messages, process them, and acknowledge completion — all via outbound-only HTTP calls.
Base URL¶
The full endpoint for a pull route is:
For example, with:
pull_api {
listen :9443
prefix /pull
auth token env:HOOKAIDO_PULL_TOKEN
}
/webhooks/github {
pull { path /github }
}
The endpoints become:
POST http://localhost:9443/pull/github/dequeuePOST http://localhost:9443/pull/github/ackPOST http://localhost:9443/pull/github/nackPOST http://localhost:9443/pull/github/extend
All requests use Content-Type: application/json.
Authentication¶
All Pull API requests require a bearer token:
Tokens are configured globally or per-route:
# Global token (applies to all pull routes)
pull_api {
auth token env:HOOKAIDO_PULL_TOKEN
}
# Per-route override
/webhooks/github {
pull {
path /pull/github
auth token env:HOOKAIDO_GITHUB_PULL_TOKEN
}
}
Per-route tokens replace (not extend) the global allowlist for that route.
Endpoints¶
POST {endpoint}/dequeue¶
Fetches a batch of messages from the queue.
Request:
| Field | Default | Description |
|---|---|---|
batch |
1 |
Number of messages to dequeue (capped by pull_api.max_batch) |
lease_ttl |
pull_api.default_lease_ttl |
How long the lease is held before auto-requeue. Capped by pull_api.max_lease_ttl |
max_wait |
pull_api.default_max_wait |
Long-poll wait if the queue is empty. Capped by pull_api.max_wait |
Response (200):
{
"items": [
{
"id": "evt_abc123",
"lease_id": "lease_xyz789",
"route": "/webhooks/github",
"target": "pull",
"payload_b64": "eyJhY3Rpb24iOiAicHVzaCJ9",
"headers": {
"Content-Type": "application/json",
"X-GitHub-Event": "push"
},
"received_at": "2026-02-09T10:00:00Z",
"attempt": 1
}
]
}
- The response always returns a
200with anitemsarray — an empty array means no messages were available. payload_b64is the base64-encoded original webhook body.headerscontains the original request headers captured at ingress (plus any forward-auth copied headers).lease_idis required for all subsequent operations (ack/nack/extend).
POST {endpoint}/ack¶
Acknowledges successful processing. The message is permanently removed from the queue.
Request:
Response: 204 No Content
POST {endpoint}/nack¶
Rejects a message, putting it back into the queue for reprocessing.
Request:
| Field | Default | Description |
|---|---|---|
lease_id |
required | The lease to reject |
delay |
0 |
Requeue delay before the message becomes visible again |
dead |
false |
If true, move to the dead-letter queue instead of requeuing |
reason |
— | Optional reason string (persisted as dead_reason when dead: true) |
- When
dead: true, thedelayis ignored and the message moves to the DLQ immediately.
Response: 204 No Content
POST {endpoint}/extend¶
Extends the lease TTL for a message that needs more processing time.
Request:
Response: 204 No Content
Lease Semantics¶
Messages use a lease-based visibility model:
- Dequeue creates a lease — the message becomes invisible to other consumers.
- The consumer has until
lease_untilto process andackthe message. - If the lease expires without an
ackornack, the message is automatically requeued. - Use
extendto renew the lease if processing takes longer than expected. nackexplicitly requeues (with optional delay) or dead-letters the message.
Invalid/expired lease operations return 409 Conflict.
Error Responses¶
All non-2xx responses return structured JSON:
| Status | Code | Meaning |
|---|---|---|
400 |
invalid_body |
Malformed JSON, unknown fields, or trailing documents |
401 |
unauthorized |
Missing or invalid bearer token |
403 |
forbidden |
Token not in allowlist for this route |
409 |
lease_expired |
Lease ID is invalid or has expired |
429 |
rate_limited |
Rate limit exceeded |
503 |
queue_overload |
Queue backend is unavailable |
Request bodies are parsed strictly: unknown JSON fields and trailing JSON documents are rejected with
400.
Dequeue Controls¶
Fine-tune Pull API behavior in the config:
pull_api {
auth token env:HOOKAIDO_PULL_TOKEN
max_batch 100 # cap per-request batch size (default 100)
default_lease_ttl 30s # when client omits lease_ttl (default 30s)
max_lease_ttl 5m # hard upper bound for lease TTL
default_max_wait 0 # when client omits max_wait (default 0 = no wait)
max_wait 30s # hard upper bound for long-poll wait
}
Consumer Implementation Tips¶
- Use long-polling (
max_wait) to reduce empty-response overhead. - Process in batches — dequeue multiple messages, process in parallel, ack individually.
- Extend leases proactively — if processing takes >50% of your
lease_ttl, extend early. - Dead-letter on permanent failures — use
nack { dead: true, reason: "..." }for non-retryable errors. - Idempotent processing — Hookaido provides at-least-once delivery, so your handler should tolerate duplicates.