HookGet עברית Start free

Delivering webhooks to a queue instead of a URL

SQS, SNS, EventBridge, Pub/Sub, Service Bus, Kafka, RabbitMQ, 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, EventBridge, Pub/Sub, Service Bus, Kafka (REST proxy), RabbitMQ (management API), 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 seven brokers, one endpoint field 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
eventbridgeAn AWS EventBridge event busAn access key that may events:PutEvents to that bus
kafkaA Kafka topic, through the Confluent REST proxyThe proxy URL and, if it wants them, basic credentials
rabbitmqA RabbitMQ exchange, through the management APIA user that may publish to that vhost
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": "[email protected]",
      "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.

AWS EventBridge

curl -X POST https://api.hookget.com/v1/endpoints \
  -H "authorization: Bearer $HOOKGET_KEY" \
  -H "content-type: application/json" \
  -d '{
    "kind": "eventbridge",
    "bus": {
      "target": "arn:aws:events:eu-central-1:123456789012:event-bus/orders",
      "access_key_id": "AKIA…",
      "secret_access_key": "…"
    }
  }'

The region comes out of the bus ARN. One honest wrinkle: EventBridge parses and re-serialises the event detail on its way to every target, so the exact signed bytes cannot travel as the detail — they travel in it, base64-encoded in a data field with the signature attributes beside it, exactly as Pub/Sub forces. Decode, then verify.

Kafka — through the REST proxy

curl -X POST https://api.hookget.com/v1/endpoints \
  -H "authorization: Bearer $HOOKGET_KEY" \
  -H "content-type: application/json" \
  -d '{
    "kind": "kafka",
    "bus": {
      "target": "https://pkc-xxxxx.eu-central-1.aws.confluent.cloud:443",
      "topic": "orders",
      "username": "CLUSTER_API_KEY",
      "password": "CLUSTER_API_SECRET"
    }
  }'

Stated plainly: we do not speak the Kafka wire protocol — that belongs to a client library this product deliberately does not take. What we speak is the Confluent REST proxy's produce API, which Confluent Cloud hosts for every cluster and self-hosters run as a sidecar. The record key is the delivery id, so a replay lands on the same partition; the value is an envelope carrying the exact signed bytes base64-encoded plus the three signature attributes. If your cluster has no HTTP door and cannot grow one, a vendor that speaks native Kafka is the honest fit.

RabbitMQ — through the management API

curl -X POST https://api.hookget.com/v1/endpoints \
  -H "authorization: Bearer $HOOKGET_KEY" \
  -H "content-type: application/json" \
  -d '{
    "kind": "rabbitmq",
    "bus": {
      "target": "https://mq.example.com",
      "entity": "webhook-events",
      "vhost": "prod",
      "routing_key": "orders",
      "username": "hookget-publisher",
      "password": "…"
    }
  }'

Publishes through the management plugin's HTTP API — per-delivery traffic, which is exactly what that API is rated for, and every hosted RabbitMQ (CloudAMQP included) exposes it over TLS. The payload travels base64 so the signed bytes survive; the signature attributes ride in the AMQP message headers where any consumer on any client library finds them. The routing key defaults to the event type, so consumers bind queues the way they already think.

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.

Start delivering webhooks today

Point your webhooks at HookGet and watch the first delivery arrive, signed, in under a minute.

Create a free account Try the free webhook tester

10,000 deliveries a month free, no credit card. The free tier blocks rather than bills, so trying it cannot produce an invoice.