HookGet Open dashboard

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.

One event, every destination kind A published event fans out to every subscribed destination: an HTTPS endpoint receives a signed POST, a queue or bus receives a message with the signature as attributes, an object store receives one object per delivery, and a polling consumer pulls from a cursor instead of being pushed to. Retries, the timeline and metering are identical for all of them. One event publish once Fan-out filter · transform · sign HTTPS endpoint signed POST, three headers Queue or bus SQS · SNS · Pub/Sub · Service Bus Object storage one object per delivery, S3-compatible Polling consumer GET /v1/poll/{id} — they pull
Every kind is an ordinary endpoint: same retries, same timeline, same metering, on every plan. The dashed arrow runs the other way on purpose — a polling destination is never pushed to; the consumer reads from a cursor when it likes.

The kinds

KindWhere the delivery landsCredentials you provide
sqsAn AWS SQS queueAn access key that may sqs:SendMessage to that queue
snsAn AWS SNS topicAn access key that may sns:Publish to that topic
pubsubA Google Pub/Sub topicA service account with roles/pubsub.publisher on the topic
servicebusAn Azure Service Bus queue or topicA SAS policy with the Send claim
bucketAn object per delivery in an S3-compatible bucketA key that may write under one prefix
pollNowhere — the consumer reads GET /v1/poll/{id} from a cursorNone. 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.