# Webhooks and event handling

Gateman is not a universal webhook receiver, event-forwarding service, or
event queue for customer applications. Its responsibilities depend on the
capability involved:

- Gateman receives and processes Amazon SES/SNS tracking notifications for an
  SES tracking setup.
- Gateman can provision a Stripe endpoint for a customer callback URL, but
  Stripe then delivers events to that customer-owned destination.
- The Gateman WordPress plugin receives Stripe payment events directly at a
  WordPress REST endpoint and completes supported local payments.
- SMS providers can deliver status callbacks directly to customer-supplied
  destinations when the selected provider request supports them.
- Square, PayPal, and other provider relays do not automatically make Gateman
  the receiver of later provider events.

A successful provider API relay request therefore does not imply that Gateman
will receive the provider's subsequent webhook or callback. The provider,
destination owner, and receiver determine the delivery and verification path.

## Webhook ownership by capability

| Capability or integration | Who receives the event? | Who verifies and processes it? |
| --- | --- | --- |
| SES tracking | Gateman at `POST /webhooks/ses/{app_id}` | Gateman verifies the SNS envelope and updates the app's email record |
| Stripe webhook setup | The callback URL supplied by the application | The application at that URL; Gateman only provisions and stores endpoint configuration |
| WordPress Stripe payments | The customer's WordPress site at `/wp-json/getgateman/v1/stripe/webhook` | The Gateman WordPress plugin verifies and completes supported local payments |
| Twilio SMS status | The `StatusCallback` URL supplied in the SMS request | The customer's callback application |
| Telnyx SMS status | The `webhook_url` or failover URL supplied in the SMS request | The customer's callback application |
| AWS SMS callbacks | No Gateman callback receiver is provided by the supported send contract | The customer must use provider-native facilities if needed |
| Square and PayPal events | A customer-configured provider destination | The customer's integration |

Provider callbacks use provider-specific authentication. They are not signed
with Gateman application credentials or automatically covered by [Contract
v1](https://docs.getgateman.com/contract-v1/).

## Premium application email destinations

Premium applications can keep Gateman tracking enabled and optionally add a
second provider-managed HTTPS destination. The provider delivers its native
payload independently to both destinations; Gateman does not relay or
transform events.

The optional destination is configured from the application console and is
never application-only. SES adds an HTTPS subscription to the existing SNS
topic and remains pending until the application completes SNS confirmation.
Postmark creates a separate webhook for the selected message stream and keeps
its provider verification state separate from Gateman's webhook. Resend
creates a separate webhook and returns its provider-issued signing secret for
the customer application to store and verify natively. Standard applications
cannot configure these destinations.

## Email tracking entitlement

Provider-event ingestion and Gateman-managed tracking are Premium features.
Standard applications may send through SES, Postmark, and Resend, but valid
provider events for Standard applications are acknowledged without being
written to Gateman's tracking ledger or suppression intelligence. Premium
applications retain the event processing, deduplication, suppression, and
activity-summary behaviour described below.

This is an additional Gateman protection layer. It does not disable or replace
provider-native bounce, complaint, reputation, or sender-policy controls.

## Events processed by Gateman

### SES delivery and tracking events

When SES tracking is provisioned for an application, SES publishes event
notifications to the configured Amazon SNS topic. The SNS subscription points
to:

```text
POST /webhooks/ses/{app_id}
```

The receiver supports these SES event types:

| SES event | Internal Gateman state | Typical effect |
| --- | --- | --- |
| `Send` | accepted | Records that SES accepted the message for sending |
| `Delivery` | delivered | Records successful provider delivery |
| `Bounce` | bounced | Records the bounce and its diagnostics |
| `Complaint` | complained | Records the complaint and its diagnostics |
| `Reject` | rejected | Records provider rejection and its diagnostics |

Provider acceptance is not delivery. A `Send` event means SES accepted the
message; it does not mean the recipient's mailbox accepted it. A later
`Delivery`, `Bounce`, `Complaint`, or `Reject` event supplies the subsequent
provider outcome when SES reports one.

Gateman verifies the SNS signature, validates the configured topic and
application binding, checks the relevant SES/AWS identity information, and
correlates the SES message ID and application tag to an existing email record.
Permanent bounces and complaints with recipient evidence can create or update
the appropriate suppression state. Suppression is provider- and event-specific;
it is not a promise that every provider rejection or temporary bounce creates a
suppression entry.

SES tracking is SES-specific: Postmark and Resend email sending do not provide
equivalent Gateman SNS event ingestion through this route.

#### SES acknowledgement, duplicates, and timing

The route returns a successful acknowledgement after a valid notification has
been processed, and also treats an already-recorded event as a duplicate rather
than applying its side effects again. Invalid notifications are rejected;
processing failures return a failure response so SNS may redeliver according to
its own delivery rules.

SNS notifications may arrive after a delay or be redelivered by AWS. Gateman
validates and processes supported notifications without assuming chronological
delivery. Event-level duplicate protection and terminal-status safeguards
prevent a duplicate or stale event from inappropriately repeating or replacing
an email outcome, but they do not create universal exactly-once delivery or
ordering.

Applications are responsible for customer-facing email history, delivery-status
tracking, event processing, statistics, and reporting. Gateman retains the
internal ledger, provider-event ingestion, correlation, deduplication,
suppression, and operational diagnostics described above.
See [Send email](https://docs.getgateman.com/capabilities/email.send/).

## Stripe webhook provisioning for customer applications

Gateman can create or reconcile one Stripe webhook endpoint per application and
Stripe environment. The authenticated application calls:

```text
POST /stripe/webhook/setup
```

The request contains the selected `environment`, an HTTPS `callback_url`, and
a required non-empty `events` array of Stripe-native event names. The callback
URL must be a valid HTTPS destination without embedded credentials. Gateman
does not maintain a provider event catalogue: custom applications choose the
event names they need, including `*` where Stripe supports it, and Stripe
validates the provider-supported selection.

Gateman removes exact duplicate event names and sorts the list
deterministically. It does not expand `*`. The canonical event list is used
for the Stripe request, reconciliation, idempotency key, persisted hash, and
response. Repeating setup with the same callback URL and event list does not
update the recorded endpoint; changing either updates it. A requested event
list is the complete desired selection, so an intentional reduction replaces
the endpoint's prior selection.

The setup response identifies the environment, endpoint ID, callback URL, event
set, and configured state. It does not return the Stripe signing secret. The
endpoint's signing secret is stored in Gateman's encrypted credential store for
later retrieval.

The application can retrieve the environment-specific secret through:

```text
POST /stripe/webhook-secret
```

The request contains only `environment`, and the successful response contains
the configured `webhook_secret`. These routes are authenticated Gateman API
requests and require the exact `stripe:webhook:write` or
`stripe:webhook:read` capability respectively. See [Manage Stripe webhook](https://docs.getgateman.com/capabilities/stripe.webhook.write/),
[Read Stripe webhook secret](https://docs.getgateman.com/capabilities/stripe.webhook.read/),
and [Authentication](https://docs.getgateman.com/authentication/).

Provisioning does not make Gateman the event receiver. Stripe sends the
configured events directly to the customer's HTTPS callback URL. That receiving
application must verify the Stripe signature using the secret, correlate the
event to its own resource, handle duplicate delivery, process only supported
business transitions, return the appropriate acknowledgement, and manage
provider retries and failures. Gateman's Stripe relay and webhook setup
capabilities do not provide a universal Stripe event ledger or payment-state
reconciliation service for arbitrary customer applications.

## WordPress Stripe payment webhooks

The canonical Gateman WordPress plugin provisions a Stripe endpoint whose
callback is normally:

```text
POST /wp-json/getgateman/v1/stripe/webhook
```

For its payment workflow, the plugin explicitly requests these events:

- `checkout.session.completed`
- `checkout.session.async_payment_succeeded`
- `payment_intent.succeeded`

These are WordPress integration requirements, not Gateman-wide defaults.

Stripe delivers payment events directly to WordPress. The plugin reads the raw
request body, verifies the Stripe signature, parses the event, and correlates
the provider metadata with a local payment, order, or transaction before
completion. It supports WooCommerce, Easy Digital Downloads, and FluentCart.
The local payment state and provider payment status are checked before the
platform completion callback runs. Repeated delivery therefore does not
blindly repeat a completed payment transition. A browser return from Checkout
or Payment Element is a separate user-navigation path; it is not a substitute
for verified webhook confirmation.

### Signing-secret retrieval modes

In GetGateman → Settings → Stripe Webhook, administrators choose how the
plugin obtains the environment-specific signing secret.

**Retrieve for every webhook — default**

- WordPress requests the secret from Gateman for every incoming webhook.
- No local secret cache is maintained.
- Each accepted secret-retrieval request is a billable Gateman operation.

**Cache for five minutes — optional**

- WordPress retrieves the secret only when no valid matching transient exists.
- The value is stored in a WordPress transient for five minutes.
- Subsequent matching events reuse that cached value during the TTL.
- Cached secrets may reside in the WordPress database or object cache; they are
  not memory-only.
- Cache entries are isolated by the relevant Gateman application, environment,
  endpoint, callback configuration, and base URL.
- Gateman application or environment changes, webhook setup changes, endpoint
  replacement, and retrieval-mode changes invalidate the relevant cache.

The choice is a trade-off between a fresh Gateman retrieval on every event and
fewer retrieval operations with short-lived local storage. The plugin does not
log or display the secret. It must obtain a valid secret before signature
verification; if retrieval fails and no valid cache is available, the request
temporarily fails. An invalid Stripe signature is rejected, and the payment
handler is not reached.

This WordPress receiver is specific to the canonical plugin. It does not imply
that non-WordPress Gateman clients have a hosted Stripe event receiver.

## SMS and other provider callbacks

Sending an SMS through Gateman and receiving its later delivery status are
separate responsibilities.

- Twilio accepts a caller-supplied `StatusCallback` URL in the SMS request.
- Telnyx accepts `webhook_url`, `webhook_failover_url`, and its provider-native
  webhook options in the SMS request.
- The supported AWS SMS send contract does not expose a Gateman-managed inbound
  callback URL.

When a supported Twilio or Telnyx callback is configured, the provider sends
notifications directly to that customer destination. Gateman does not receive,
verify, forward, or store those inbound callbacks as a general SMS event
service. The customer application must apply the provider's signature and
authentication rules, correlate the notification to its message, handle
duplicates and retries, and distinguish an accepted send response from later
delivery status. Staging and development SMS capture records the outbound
request locally; it does not create provider callbacks.

Square and PayPal API relay requests likewise do not automatically subscribe
Gateman to later provider events. Customers needing Square or PayPal
notifications must configure the provider destination and receiver appropriate
to their integration. Stripe's provisioning contract does not apply to these
providers.

## Authentication and signatures

There are two separate authentication boundaries:

1. An application authenticates requests it makes to Gateman with Contract v1,
   including the application headers and signature rules described in
   [Authentication](https://docs.getgateman.com/authentication/) and [Contract v1](https://docs.getgateman.com/contract-v1/).
2. A provider authenticates event deliveries using its own webhook or SNS
   contract.

Gateman verifies the SNS envelope for the SES route. A customer endpoint
verifies Stripe signatures for a provisioned non-WordPress callback. The
WordPress plugin verifies Stripe signatures for its own REST endpoint. Customer
Twilio, Telnyx, Square, PayPal, or other provider receivers must implement the
verification rules required by those providers. Gateman application credentials
must not be used as provider webhook secrets.

## Duplicate delivery, ordering, and idempotency

Webhook consumers should expect:

- duplicate delivery;
- delayed delivery;
- out-of-order events;
- provider redelivery after an unsuccessful acknowledgement; and
- uncertainty when processing succeeds but the acknowledgement is lost.

Event deduplication means recognising a provider event that has already been
seen. Business-operation idempotency means ensuring that the event cannot
complete or mutate the same payment or resource twice. They are related but not
identical.

Gateman's implemented protections are capability-specific:

- SES records an event key and applies event-level duplicate protection plus
  internal email-event safeguards.
- The WordPress plugin relies on local order, payment, and transaction state
  checks around its supported completion handlers; it does not maintain a
  universal Stripe event ledger.
- Customer-created webhook receivers must choose and persist the provider event
  identifiers and business idempotency state appropriate to their application.

See [Idempotency](https://docs.getgateman.com/idempotency/) for the distinction
between request idempotency, provider-native controls, and business effects.

## Acknowledgements, retries, and recovery

A successful HTTP acknowledgement tells the provider that the receiver
accepted or handled the event according to that receiver's contract. A failed
acknowledgement may cause provider redelivery according to provider-specific
rules. Gateman does not control those schedules.

A webhook timeout does not prove that processing failed. The receiver should
make its business effect safe to retry, record enough provider identity to
recognise duplicates, and distinguish delayed processing from an absent event.
Neither Gateman's SES route nor the WordPress plugin provides a general replay
API, dead-letter queue, outbound delivery queue, or universal reconciliation
worker for customer webhooks.

For SES, valid notifications and recognised duplicates receive a successful
route response; malformed notifications are rejected and processing failures
return a failure response. AWS SNS decides whether and when to redeliver. For
WordPress Stripe payments, signature verification and local payment validation
occur at the WordPress endpoint before the platform completion handler; failure
returns an unsuccessful REST response rather than silently accepting an
unverified payment.

## Environments

### Stripe

Stripe webhook setup and secret retrieval are environment-scoped. Production,
staging, and development endpoint state must not be conflated. The callback
receiver must use the secret for the same environment that Stripe is delivering
from. The WordPress plugin selects the configured Gateman application
environment, and its cache isolation includes that environment.

The effective Stripe live or test mode is determined by the configured Stripe
credential used for the selected Gateman environment. Confirm the provider
account and mode before provisioning an endpoint.

### SES

SES tracking is bound to the configured Gateman application, AWS account,
region, SES configuration set, and SNS topic. The SES receiver validates that
the notification belongs to the configured application/topic binding. It does
not treat SES SNS delivery as a generic production/staging/development
application request field.

### Other providers

Provider callback environments do not automatically follow Gateman's
environment and credential-pairing model. Follow each provider's own callback
and signing documentation.

## Operation accounting

Inbound provider delivery to Gateman is not itself a billable Gateman operation.
The following customer-initiated Gateman calls are billable when accepted into
their accounting lifecycle:

| Operation | Accounting |
| --- | --- |
| Stripe webhook setup | One `stripe.webhook.setup` operation |
| Stripe webhook-secret retrieval | One `stripe.webhook_secret.read` operation |
| Inbound SES/SNS ingestion | No separate Gateman operation |
| Provider webhook delivered directly to a customer application | No Gateman inbound-event operation |
| WordPress webhook processing | No separate Gateman webhook-processing operation |
| WordPress signing-secret retrieval | One Gateman secret-retrieval operation when the plugin makes the request |
| WordPress verification using a valid five-minute cached secret | No new secret-retrieval operation |

With WordPress's default per-event mode, multiple Stripe deliveries can cause
multiple secret-retrieval operations. With the five-minute cache, a new
operation is used only when the plugin actually retrieves a new secret; cache
reuse does not call Gateman. See [Operation Accounting](https://docs.getgateman.com/operation-accounting/)
for authoritative accounting and failure semantics.

## Event visibility, retention, and limitations

For SES, Gateman retains internally recorded message state, supported event
history, provider diagnostics where available, and suppression state. These
remain internal operational data; customer-facing history, reporting, and
retention decisions belong to the application. See [Data retention](https://docs.getgateman.com/data-retention/).

For Stripe endpoints owned by customer applications, delivery logs, event
history, replay, retention, and reconciliation remain the responsibility of the
receiving application and Stripe. Gateman exposes endpoint configuration and
secret retrieval, not a universal customer event history.

For WordPress, payment/order state and any local logs belong to the WordPress
installation and its commerce plugins. The Gateman plugin does not expose a
universal event ledger.

## Related references

- [Authentication](https://docs.getgateman.com/authentication/)
- [Contract v1](https://docs.getgateman.com/contract-v1/)
- [Environments](https://docs.getgateman.com/environments/)
- [Operation Accounting](https://docs.getgateman.com/operation-accounting/)
- [Idempotency](https://docs.getgateman.com/idempotency/)
- [Errors and responses](https://docs.getgateman.com/errors-and-responses/)
- [Send email](https://docs.getgateman.com/capabilities/email.send/)
- [Send SMS](https://docs.getgateman.com/capabilities/sms.send/)
- [Use Stripe](https://docs.getgateman.com/capabilities/stripe.use/)
- [Manage Stripe webhook](https://docs.getgateman.com/capabilities/stripe.webhook.write/)
- [Read Stripe webhook secret](https://docs.getgateman.com/capabilities/stripe.webhook.read/)
- [GetGateman for WordPress](https://docs.getgateman.com/wordpress/)
