Delivering webhooks to a queue instead of a URL
SQS, SNS, Pub/Sub, Service Bus, S3-compatible buckets and a polling endpoint — for the consumer who will not open an inbound port.
In short
- A destination does not have to be a URL: SQS, SNS, Pub/Sub, Service Bus, an S3-compatible bucket, or a polling endpoint the consumer reads.
- Every kind is available on every plan — this is not an enterprise tier.
- The Standard Webhooks signature travels as message attributes, so a queue consumer verifies exactly what an HTTP consumer verifies.
- A failed publish to a broker is an ordinary delivery failure: same retry schedule, same timeline, same dead-letter queue.
Why a URL is sometimes the wrong ask
The request usually arrives from your customer, not from you: “can you put the events on our SQS queue?” Sometimes it is architecture — they already consume everything through a broker and a lone HTTPS handler would be the odd one out. Just as often it is policy: their security team will not open an inbound port for a third party, and no amount of IP allowlisting changes the answer. Either way, if all you can send is HTTP, their request becomes your engineering project.
Here it is a field on the endpoint. Fan-out, retries, the delivery timeline, replay and metering are identical for every destination kind — the transport is the only thing that changes.
The kinds
| Kind | Where the delivery lands | Credentials you provide |
|---|---|---|
sqs | An AWS SQS queue | An access key that may sqs:SendMessage to that queue |
sns | An AWS SNS topic | An access key that may sns:Publish to that topic |
pubsub | A Google Pub/Sub topic | A service account with roles/pubsub.publisher on the topic |
servicebus | An Azure Service Bus queue or topic | A SAS policy with the Send claim |
bucket | An object per delivery in an S3-compatible bucket | A key that may write under one prefix |
poll | Nowhere — the consumer reads GET /v1/poll/{id} from a cursor | None. The consumer authenticates with a portal grant |
SQS
curl -X POST https://api.hookget.com/v1/endpoints \
-H "authorization: Bearer $HOOKGET_KEY" \
-H "content-type: application/json" \
-d '{
"kind": "sqs",
"event_types": ["order.created"],
"bus": {
"target": "https://sqs.eu-central-1.amazonaws.com/123456789012/orders",
"access_key_id": "AKIA…",
"secret_access_key": "…"
}
}'
The region is derived from the queue URL, so there is one less field to get wrong. The right
credential is a user or role whose entire policy is sqs:SendMessage on that one
queue — we are a publisher to it, and a publisher needs nothing else.
SNS
curl -X POST https://api.hookget.com/v1/endpoints \
-H "authorization: Bearer $HOOKGET_KEY" \
-H "content-type: application/json" \
-d '{
"kind": "sns",
"bus": {
"target": "arn:aws:sns:eu-central-1:123456789012:orders",
"access_key_id": "AKIA…",
"secret_access_key": "…"
}
}'
The topic ARN carries the region in its fourth field, and that is where we read it from.
Google Pub/Sub
curl -X POST https://api.hookget.com/v1/endpoints \
-H "authorization: Bearer $HOOKGET_KEY" \
-H "content-type: application/json" \
-d '{
"kind": "pubsub",
"bus": {
"project": "acme-prod",
"topic": "orders",
"client_email": "hookget-publisher@acme-prod.iam.gserviceaccount.com",
"private_key": "-----BEGIN PRIVATE KEY-----\n…"
}
}'
We mint the OAuth2 token from the service account key the same way Google’s own libraries do, per delivery batch, and the key itself is encrypted at rest and never returned by any read.
Azure Service Bus
curl -X POST https://api.hookget.com/v1/endpoints \
-H "authorization: Bearer $HOOKGET_KEY" \
-H "content-type: application/json" \
-d '{
"kind": "servicebus",
"bus": {
"namespace": "acme.servicebus.windows.net",
"entity": "orders",
"key_name": "hookget-send",
"sas_key": "…"
}
}'
Create a SAS policy on the queue or topic with only the Send claim and name it for us, so revoking it later is one click that cannot break anything else.
An S3-compatible bucket
curl -X POST https://api.hookget.com/v1/endpoints \
-H "authorization: Bearer $HOOKGET_KEY" \
-H "content-type: application/json" \
-d '{
"kind": "bucket",
"bucket": {
"bucket": "acme-events",
"prefix": "hookget/",
"region": "eu-central-1",
"access_key_id": "AKIA…",
"secret_access_key": "…"
}
}'
Each delivery becomes one object under the prefix. “S3-compatible” is literal: anything that speaks the S3 API with SigV4 works, not only AWS.
Polling — no destination at all
The last option removes the destination entirely. An endpoint of kind poll is
fanned out to like any other, but nothing is sent: the consumer reads
GET /v1/poll/{id} from a cursor whenever it likes, authenticated with a portal
grant. It sees precisely the set of events a pushed endpoint would have received — same event
types, same filters, same transformations.
curl -X POST https://api.hookget.com/v1/endpoints \
-H "authorization: Bearer $HOOKGET_KEY" \
-H "content-type: application/json" \
-d '{"kind": "poll", "event_types": ["order.created"]}'
The signature still travels
There are no HTTP headers on the far side of a queue, so the three Standard Webhooks values —
webhook-id, webhook-timestamp, webhook-signature — travel
as message attributes, alongside hookget-event-type. The message body is the exact
bytes that were signed. A consumer reading the queue verifies with the same function an HTTP
consumer uses; the signature guide has it in full.
Failure is boring here, on purpose. A broker that refuses a publish — bad credentials, deleted queue, throttling — is recorded as an ordinary failed attempt with the broker's answer in the log, retried on the endpoint's ordinary schedule, and dead-lettered when the schedule is spent. There is no separate machinery to learn.
Queue, bucket and polling destinations are included on every HookGet plan. Elsewhere in this category they tend to arrive with the enterprise tier — worth knowing before you architect around their absence.