A pull consumer runs in your own code. It receives messages from https://queues-live.ezghcloud.com
with an API key, handles them, and acknowledges each one it finished with its receipt handle. It
needs queues.messages.receive on the queue, and outbound HTTPS only.
Receive messages
curl -sS -X POST "https://queues-live.ezghcloud.com/v1/organizations/$ORG_ID/projects/$PROJECT_ID/queues/$QUEUE_ID/messages/receive" \
-H "Authorization: Bearer $EZGH_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "maxMessages": 10, "waitSeconds": 20, "visibilityTimeoutSeconds": 60 }'ReceiveMessages takes, all optional:
| Field | Default | Range |
|---|---|---|
maxMessages |
1 | 1 to 10 |
waitSeconds |
The queue’s receiveWaitSeconds |
0 to 20 |
visibilityTimeoutSeconds |
The queue’s visibilityTimeoutSeconds |
0 to 43,200 |
It answers 200 with up to maxMessages messages as soon as there are any, or with
{"messages": []} when the wait ends. Give your HTTP client a timeout longer than waitSeconds.
Each message has:
| Field | Meaning |
|---|---|
messageId |
The message’s ID, the same on every delivery |
receiptHandle |
Acknowledges or changes the visibility of this delivery |
body, attributes |
As sent. A dead-lettered message adds ezgh. attributes (Dead-letter queues) |
sentAt |
When it was sent |
firstReceivedAt |
When it was first received |
receiveCount |
How many times it has been received, this time included |
groupId, seq |
FIFO queues only: its message group, and its sequence number in the group (a string of digits) |
A receive can answer fewer messages than are visible. With many messages in flight (120,000 on a standard queue, 20,000 on a FIFO queue) it answers no more until some are acknowledged or become visible again.
From the CLI, ezgh queues receive takes --max, --wait
and --visibility-timeout, and prints each message with its receipt handle; --ack acknowledges
them once printed. ezgh queues ack and
ezgh queues change-visibility take receipt handles.
Acknowledge messages
When you’ve handled a message, acknowledge it, which deletes it:
curl -sS -X POST https://queues-live.ezghcloud.com/v1/receipts/ack \
-H "Content-Type: application/json" \
-d '{ "receiptHandles": ["AQAAAH8…", "AQAAAJ2…"] }'{ "results": [ { "ok": true }, { "error": "receipt_handle_stale" } ] }- No credential.
POST /v1/receipts/ackreads no API key or session: the receipt handle is what allows it. Browser code on any origin can call it, without credentials. - 1 to 10 handles a request, from any of your queues.
- One result per handle, in order:
{"ok": true}, or{"error": …}:
| Error | Meaning | What to do |
|---|---|---|
receipt_handle_stale |
The handle’s delivery is over: the message was received again, already acknowledged, moved to a dead-letter queue, purged, expired, or its queue was deleted. Nothing changed | Don’t retry. If it was received again, that delivery will handle it |
receipt_handle_invalid |
Not a receipt handle, or altered | Check that you pass the handle exactly as received |
The whole request answers 400 with 0 or more than 10 handles. A client address that sends more
than 100 invalid handles a minute gets 429 with Retry-After.
A handle still works after the visibility timeout ends, until another receive takes the message.
Acknowledging the same delivery twice answers receipt_handle_stale the second time.
Extend or release a message
Change how long messages stay in flight, counted from now:
curl -sS -X POST https://queues-live.ezghcloud.com/v1/receipts/visibility \
-H "Content-Type: application/json" \
-d '{ "entries": [ { "receiptHandle": "AQAAAH8…", "visibilityTimeoutSeconds": 120 } ] }'- Extend a message your consumer needs longer for, before its visibility timeout ends. A message stays in flight for at most 12 hours from its receive: a longer extension ends there.
- Release a message your consumer won’t handle with
"visibilityTimeoutSeconds": 0. It’s visible at once to the next receive. visibilityTimeoutSecondsis 0 to 43,200. Requests take 1 to 10 entries and answer the sameresultsas acknowledging, with the same errors and limits.
A consumer loop
This TypeScript loop runs with Bun, or Node.js 24 or later. Messages it fails to handle stay in flight until their visibility timeout ends, then are received again: the timeout is the delay between attempts. Give the queue a dead-letter queue so a message that always fails stops being retried.
const live = "https://queues-live.ezghcloud.com"
const { EZGH_API_KEY, ORG_ID, PROJECT_ID, QUEUE_ID } = process.env
const receiveUrl = `${live}/v1/organizations/${ORG_ID}/projects/${PROJECT_ID}/queues/${QUEUE_ID}/messages/receive`
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
async function handle(message: { messageId: string; body: string; attributes: Record<string, string> }) {
console.log(message.messageId, message.body)
}
while (true) {
const res = await fetch(receiveUrl, {
method: "POST",
headers: { Authorization: `Bearer ${EZGH_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ maxMessages: 10, waitSeconds: 20 }),
signal: AbortSignal.timeout(30_000),
})
if (res.status === 429 || res.status >= 500) {
await sleep(Number(res.headers.get("Retry-After") ?? 1) * 1000)
continue
}
if (!res.ok) throw new Error(`receive: ${res.status} ${await res.text()}`)
const { messages } = await res.json()
const handled: string[] = []
for (const message of messages) {
try {
await handle(message)
handled.push(message.receiptHandle)
} catch (err) {
console.error(`message ${message.messageId} failed (receive ${message.receiveCount})`, err)
}
}
if (handled.length > 0) {
await fetch(`${live}/v1/receipts/ack`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ receiptHandles: handled }),
})
}
}Run several consumers to handle more: each receive claims different messages. On a FIFO queue, each message group goes to one consumer at a time (FIFO queues).
Errors
| Status | When |
|---|---|
400 |
A field is out of range |
401, 403 |
The credential is missing or invalid, or lacks queues.messages.receive on the queue |
404 |
No such queue in the project, or it was deleted while the receive waited |
429 |
Over the queue’s or organization’s rate limit. Wait for Retry-After |
503 |
Try again after Retry-After |
Pull consumers and subscriptions
A queue with a push subscription can still be received from. The subscription and pull consumers then compete for the queue’s messages: each message goes to whichever claims it first.