Gareth Wilson Gareth Wilson

Guide to Amazon EventBridge Webhooks: Features and Best Practices

Published


Amazon EventBridge is AWS's serverless event bus. AWS services, SaaS partners and your own applications put events on a bus, and rules route them to targets. When the target is your own HTTP service rather than a Lambda function or a queue, EventBridge calls it through an API destination, which is how EventBridge does webhooks.

This guide covers how API destinations and connections fit together, what the requests look like, how to authenticate them with a Basic connection, the timeout and retry rules you need to plan for, and the practices that keep an EventBridge receiver reliable in production.

What are Amazon EventBridge webhooks?

An API destination is an HTTPS endpoint that EventBridge invokes as the target of an event bus rule, a pipe or a Custom Event Bus subscriber. When an event matches the rule's event pattern, EventBridge sends it to the URL with the HTTP method you chose for the destination.

Every API destination uses a connection, which holds the authorization method and credentials. Connections support three methods: Basic, API Key and OAuth Client Credentials. For Basic and API Key, EventBridge builds the authorization header itself from the values you stored, and for OAuth it exchanges a client ID and secret for an access token and manages the token. EventBridge keeps the credentials in AWS Secrets Manager.

There is no signature. EventBridge adds no HMAC header and no timestamp, and API destinations do not support mutual TLS. With a Basic connection, the Authorization header is the only thing that tells your endpoint a request came from your EventBridge account.

Amazon EventBridge webhook features

FeatureDetails
ConfigurationA connection, an API destination (URL, method, invocation rate) and a rule, pipe or Custom Event Bus subscriber that targets it
AuthenticationConnection credentials: Basic, API Key or OAuth Client Credentials. No signature
HTTP methodAny except CONNECT and TRACE. PUT and POST are the most common
BodyThe matched event envelope, or the output of an input transformer
Fixed headersUser-Agent: Amazon/EventBridge/ApiDestinations, Content-Type: application/json; charset=utf-8 unless you set one, and Range: bytes=0-1048575
Timeout5 seconds
Retries401, 407, 409, 429, 5xx and timeouts, honouring Retry-After. Event buses default to 24 hours and 185 attempts
After retriesThe event goes to a dead-letter queue if one is configured, otherwise it is dropped
Rate limitingAn invocation rate per second that you set on the destination
EndpointHTTPS, public or private. No mTLS
Source IPsNot published

User-Agent and Range cannot be overridden, and EventBridge strips a list of other headers from every request, including Origin, Referer and Forwarded.

Events and payloads

EventBridge has no webhook event list of its own. The API destination receives whatever the rule's event pattern matches, from any AWS service, SaaS partner or application that puts events on the bus.

Without an input transformer, the body is the EventBridge event envelope. Here is an EC2 state-change event:

{
  "version": "0",
  "id": "7bf73129-1428-4cd3-a780-95db273d1602",
  "detail-type": "EC2 Instance State-change Notification",
  "source": "aws.ec2",
  "account": "123456789012",
  "time": "2019-10-02T17:40:17Z",
  "region": "us-east-1",
  "resources": ["arn:aws:ec2:us-east-1:123456789012:instance/i-abcd1111"],
  "detail": { "instance-id": "i-abcd1111", "state": "pending" }
}

Route on source and detail-type together. AWS service events have a source that starts with aws., and your own custom events use whatever source and detail-type your application puts on the bus. The id is unique per event, which makes it the natural deduplication key.

An input transformer replaces the envelope with a template of your own, which can produce JSON or plain text. If you use one, your receiver parses whatever the template builds, and it only has a usable event ID if you include id in the template. A GET or DELETE destination normally sends no body at all.

Inspect EventBridge events as they arrive. Point an API destination at a Hookdeck Inspect URL to see the exact headers and body EventBridge sends, with no account or setup required.

Setting up Amazon EventBridge webhooks

You choose the credentials. Pick a username and generate a long random password, for example with openssl rand -hex 32, and store both on your receiver.

  1. In the EventBridge console, open API destinations, select the Connections tab and click Create connection.
  2. Name the connection, choose Basic as the authorization type, and enter the username and password. Your AWS account needs Secrets Manager permissions, which AmazonEventBridgeFullAccess includes.
  3. Open the API destinations tab and click Create API destination. Enter your endpoint URL, choose the HTTP method, set an invocation rate limit if you need one, and select the connection.
  4. Create a rule on your event bus with an event pattern, for example {"source": ["aws.ec2"], "detail-type": ["EC2 Instance State-change Notification"]}, and choose EventBridge API destination as the target. Add an input transformer only if your endpoint needs a different shape.
  5. Configure the rule target's retry policy and, ideally, a dead-letter queue.

To put Hookdeck in front of your service, create a Hookdeck source with the Amazon EventBridge type and use its URL as the API destination endpoint. The source asks for the Username and Password you entered on the connection. It pre-selects POST. If your API destination uses PUT, PATCH, GET or DELETE, allow that method on the source too, or Hookdeck will not accept the requests.

For local development, hookdeck listen 3000 aws-eventbridge --path /webhooks/aws-eventbridge gives you a public HTTPS URL that forwards to your machine. EventBridge cannot reach localhost directly.

Securing Amazon EventBridge webhooks

With a Basic connection, every request carries Authorization: Basic base64(username:password). Decode it, split on the first colon (a password can contain colons, a username cannot), and compare the username and password with your stored values in constant time. Evaluate both comparisons before deciding, and reject everything if the credentials are not configured.

const crypto = require('crypto');
const express = require('express');

function safeEqual(a, b) {
  // timingSafeEqual throws on unequal lengths, so check first.
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

function verifyBasicAuth(header, username, password) {
  if (!username || !password) return false; // never accept when unconfigured
  const match = /^basic\s+([A-Za-z0-9+/]+={0,2})\s*$/i.exec(header || '');
  if (!match) return false;
  const decoded = Buffer.from(match[1], 'base64');
  const colon = decoded.indexOf(0x3a); // split on the first colon: passwords may contain ':'
  if (colon === -1) return false;
  const userOk = safeEqual(decoded.subarray(0, colon), Buffer.from(username, 'utf8'));
  const passOk = safeEqual(decoded.subarray(colon + 1), Buffer.from(password, 'utf8'));
  return userOk && passOk; // evaluate both before deciding
}

const app = express();

// The API destination's method is configurable, so accept the ones you use.
app.all('/webhooks/aws-eventbridge', express.raw({ type: '*/*' }), (req, res) => {
  const ok = verifyBasicAuth(
    req.get('authorization'),
    process.env.AWS_EVENTBRIDGE_WEBHOOK_USERNAME,
    process.env.AWS_EVENTBRIDGE_WEBHOOK_PASSWORD
  );
  if (!ok) return res.sendStatus(401);

  const raw = Buffer.isBuffer(req.body) ? req.body.toString('utf8') : '';
  let event = {};
  try {
    event = raw ? JSON.parse(raw) : {};
  } catch {
    // An input transformer can emit plain text. Handle it here if you use one.
  }

  // Route on event.source and event['detail-type']; deduplicate on event.id.
  res.sendStatus(200);
});

The same check in Python:

import base64
import binascii
import hmac
import os
import re

BASIC_PATTERN = re.compile(r"^basic\s+([A-Za-z0-9+/]+={0,2})\s*$", re.IGNORECASE)


def verify_basic_auth(header, username, password) -> bool:
    if not username or not password:
        return False  # never accept when unconfigured
    match = BASIC_PATTERN.match(header or "")
    if not match:
        return False
    try:
        decoded = base64.b64decode(match.group(1), validate=True)
    except (binascii.Error, ValueError):
        return False
    received_user, sep, received_password = decoded.partition(b":")  # first colon only
    if not sep:
        return False
    # Compare bytes: compare_digest raises TypeError on a non-ASCII str.
    user_ok = hmac.compare_digest(received_user, username.encode("utf-8"))
    password_ok = hmac.compare_digest(received_password, password.encode("utf-8"))
    return user_ok and password_ok


# verify_basic_auth(request.headers.get("authorization"),
#                   os.environ.get("AWS_EVENTBRIDGE_WEBHOOK_USERNAME"),
#                   os.environ.get("AWS_EVENTBRIDGE_WEBHOOK_PASSWORD"))

Be clear about what Basic auth gives you. It proves the sender knows the connection's credentials. It does not cover the body, so it cannot detect a modified payload, and there is no timestamp, so a captured request can be replayed and will still pass. Use a long random password, serve the endpoint over HTTPS only (EventBridge requires it), and rotate the credentials if they might have leaked. The User-Agent header is easy to fake and proves nothing.

Make EventBridge events production-ready. Hookdeck Event Gateway checks the connection's Basic credentials, queues every event durably, and delivers it to your service with retries and replay you control.

Amazon EventBridge webhook limitations and pain points

A static credential instead of a signature

The Problem: Nothing in an API destination request proves the body is unchanged or that the request is new. Anyone who learns the connection's credentials can send requests your endpoint will accept.

Why It Happens: Connections hold credentials for EventBridge to present, and EventBridge computes nothing over the body. API Key connections are just as static, and OAuth connections send a token your endpoint would have to validate against your own identity provider.

Workarounds:

  • Generate a long random password and keep it out of source control and logs.
  • Treat event contents as untrusted until validated, and make handlers idempotent so a replayed request does no harm.
  • For high-value actions, look the resource up through the AWS API before acting on the event.

How Hookdeck Can Help: Hookdeck's Amazon EventBridge source checks the Basic username and password on every request. Hookdeck still answers with a 200, but a request with missing or wrong credentials is recorded as failed verification and never reaches your destination, so your service only processes authenticated events.

A five-second timeout

The Problem: Handlers that do real work inline time out, and EventBridge retries them, so slow processing turns into duplicate deliveries.

Why It Happens: EventBridge gives an API destination 5 seconds to respond. A timed-out request is retried under the target's retry policy, which defaults to 24 hours and 185 attempts on an event bus.

Workarounds:

  • Authenticate, store the event, return 200, and process afterwards.
  • Deduplicate on the envelope id, since a request that timed out on your side may still have been processed.

How Hookdeck Can Help: Hookdeck responds to EventBridge as soon as the request arrives and stores it, then delivers to your service with its own timeout and retries, so a slow handler no longer burns EventBridge's retry budget.

Events are dropped once retries run out

The Problem: An outage longer than the retry window loses events, and a backlog can exceed it even without an outage.

Why It Happens: EventBridge retries 401, 407, 409, 429 and 5xx responses until the event's maximum age or attempt count, then sends the event to a dead-letter queue if you configured one, and otherwise drops it. Other 4xx responses are not retried at all. If the destination's invocation rate is far below the rate of matching events, AWS warns that the backlog can exceed the 24-hour window on its own.

Workarounds:

  • Configure a dead-letter queue on every API destination target, and alert on messages arriving in it.
  • Set the invocation rate to what your endpoint can sustain, and filter the event pattern down to what you need.
  • Don't return a non-retried 4xx for a transient problem in your own code.

How Hookdeck Can Help: Hookdeck accepts each event at the rate EventBridge sends it and delivers to your service at a max delivery rate you set. Issues alert you when deliveries fail, and you can replay any event once your service is back.

The method and body are configuration, not a contract

The Problem: A receiver written for POST and the event envelope breaks when someone changes the destination to PUT or adds an input transformer.

Why It Happens: API destinations accept any method except CONNECT and TRACE, and an input transformer can turn the body into any JSON or text, or a GET can send none.

Workarounds:

  • Accept the methods you configure on the route, and handle an empty body.
  • Version input transformer templates like an API schema, and include id, source and detail-type in them.

How Hookdeck Can Help: Hookdeck's transformations can reshape events after they arrive, so the input transformer can stay simple, and the source's allowed methods make the accepted verbs explicit.

Best practices

Use one connection per receiver

Credentials live on the connection, and several API destinations can share one. A connection per receiving service lets you rotate one set of credentials without touching the others. When you rotate, update the connection and your receiver together.

Keep the envelope unless you need a transformer

The default envelope carries id, source, detail-type and time, which is everything you need to route, deduplicate and order events. If you add an input transformer, keep those fields in its output.

Deduplicate on id

Retries after timeouts and 5xx responses mean the same event can arrive more than once. Store the envelope id and skip repeats. See our guide to webhook idempotency.

Acknowledge fast and process asynchronously

Five seconds is a short window. Verify, persist, return 200, and do the work afterwards. See why to process webhooks asynchronously.

Always configure a dead-letter queue

Without one, an event that exhausts its retries is gone. With one, you can inspect and redrive it.

Conclusion

EventBridge API destinations deliver bus events to your HTTPS endpoint with whatever method you choose, authorized by a connection rather than a signature. With a Basic connection, compare the username and password in constant time over HTTPS, deduplicate on the envelope id, answer within 5 seconds, and give every target a dead-letter queue.

Hookdeck Event Gateway checks the connection's credentials, absorbs bursts, and holds each event until your service has processed it, so a slow handler or an outage on your side costs latency instead of dropped events.

Get started with Hookdeck for free and handle Amazon EventBridge events reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.