Gateman
Documentation/Manage Stripe webhook

Manage Stripe webhook

capability_id: stripe.webhook.write
display_name: Manage Stripe webhook
ui_capability_key: manage_stripe_webhook
scope: stripe:webhook:write
endpoint:
  method: POST
  path: /stripe/webhook/setup
operation: stripe.webhook.setup
billable: true
environments:
  - production
  - staging
  - development

Purpose and authorization

stripe.webhook.write authorizes an authenticated Gateman application to configure one supported Stripe webhook destination for its own application. The application supplies the callback URL and Stripe event subscriptions; Gateman uses the application's configured Stripe credentials to perform the supported endpoint setup.

This capability requires the stripe:webhook:write scope and is deliberately narrower than stripe:use. It does not grant native Stripe API relay access, general Stripe resource management, or access to another application's resources.

The callback URL belongs to the receiving application. Stripe sends webhook deliveries directly to that URL. Gateman does not receive, relay, process, deduplicate, or verify those deliveries. The receiving application must validate Stripe signatures and process events itself. An application that needs the corresponding signing secret can use the separate stripe.webhook.read capability when granted its independent scope.

Implementation contract

The request is an authenticated Contract v1 request:

POST /stripe/webhook/setup
Content-Type: application/json
X-GM-App: app_example
X-GM-Ts: 1791748800
X-GM-Sig: <Contract-v1-signature>
X-GM-Idempotency-Key: optional-gateman-key

X-GM-Idempotency-Key is optional. If supplied, it is part of the signed Contract v1 request material; it is not a Gateman replay or billing ledger for this capability.

The JSON body accepts exactly these three fields:

{
  "environment": "production",
  "callback_url": "https://example.com/webhooks/stripe",
  "events": [
    "checkout.session.completed",
    "payment_intent.succeeded"
  ]
}

Unknown fields are rejected. The endpoint does not accept an arbitrary Stripe endpoint ID, a submitted signing secret, a Stripe account selector, or other provider-management parameters.

environment

The public environment label is required and is trimmed and lowercased. It must be one of production, staging, or development.

Public labelStripe modeStripe API credentialWebhook secret slot
productionLiveSTRIPE_SECRET_KEYSTRIPE_WEBHOOK_SECRET
stagingTestSTRIPE_SECRET_KEY_TESTSTRIPE_WEBHOOK_SECRET_TEST
developmentTestSTRIPE_SECRET_KEY_TESTSTRIPE_WEBHOOK_SECRET_TEST

staging and development are interchangeable public labels for the same logical Gateman test environment. They share one tracked Stripe test endpoint association and do not provision independent test endpoints. The public label is retained in the successful response; it does not change the underlying Stripe live/test mode.

Staging and development are real Stripe test-mode operations, not local simulation or capture modes.

callback_url

callback_url must be a string that:

  • is no longer than 2,048 bytes;
  • has no leading or trailing whitespace;
  • contains no ASCII whitespace or control character, including DEL;
  • parses as a URL with an https scheme;
  • has a nonempty valid hostname; and
  • does not contain embedded username or password credentials.

Query strings and fragments are accepted by this validation. Gateman does not verify DNS, public reachability, private-network status, or whether the application actually handles the callback.

events

events must be a nonempty JSON array. Each element must be a string that is nonempty, no longer than 255 bytes, and contains no ASCII whitespace or control character. Duplicate event names are removed and the remaining names are sorted lexicographically before configuration and in the success response.

The wildcard event name * is accepted by Gateman's request validation. Stripe remains responsible for accepting or rejecting the selected event configuration and for defining supported event names.

Authentication and authorization

The application must use Contract v1 and be granted exactly the capability needed for this operation: stripe:webhook:write.

Write does not require stripe:webhook:read, and Read does not require Write. Neither scope grants stripe:use. Use the Official PHP client, Official Node.js client, Official Python client, or Official Go client, or implement the same Contract v1 signing rules directly.

The application must sign the exact JSON bytes sent in the request body. Do not replace the Contract v1 headers with a bearer token or an Authorization header of the application's own.

Setup and reconciliation behaviour

Gateman exposes one supported live association and one supported test association per application. It does not provide general-purpose listing, adoption, deletion, rotation, or multi-endpoint management.

No tracked endpoint

Gateman creates a Stripe webhook endpoint with the supplied callback URL and canonical event list. Stripe returns a signing secret on creation; Gateman stores that secret in the selected live or test webhook-secret slot.

Existing tracked endpoint

Gateman checks the previously tracked endpoint. If its callback URL or event subscriptions differ, Gateman updates that endpoint. An ordinary update does not replace the existing stored signing secret.

If the tracked endpoint already matches the requested callback URL and events, Gateman may complete without modifying Stripe. The request remains an admitted stripe.webhook.setup operation.

If Stripe reports that the previously tracked endpoint no longer exists, Gateman creates a replacement endpoint and stores the newly returned signing secret in the selected slot. The previous stored value is therefore replaced for that live/test mode.

Alternating between staging and development does not by itself make an otherwise valid test endpoint appear unconfigured or create a second test endpoint.

Manual webhook configuration

Customers may create a Stripe webhook endpoint directly in Stripe and store its signing secret in Gateman. Manual storage requires only the secret value; it does not require an endpoint URL or endpoint ID. The supported names are:

  • STRIPE_WEBHOOK_SECRET for live/production;
  • STRIPE_WEBHOOK_SECRET_TEST for test/staging/development.

A manually stored secret can exist without a Gateman-tracked endpoint. Write does not automatically adopt a manually created Stripe endpoint based on its URL, and Gateman cannot identify an endpoint from a secret value alone.

When Write creates an endpoint, the Stripe-issued secret overwrites the corresponding stored slot. When Write updates an existing endpoint or finds that its configuration is unchanged, the existing stored secret is preserved. Applications do not need Write in order to use the separate Read capability.

Success response

A successful setup returns HTTP 200:

{
  "ok": true,
  "environment": "production",
  "endpoint_id": "we_example",
  "callback_url": "https://example.com/webhooks/stripe",
  "events": [
    "checkout.session.completed",
    "payment_intent.succeeded"
  ],
  "configured": true
}

The fields mean:

FieldMeaning
okThe setup operation completed successfully.
environmentThe accepted, normalized public environment label supplied by the caller.
endpoint_idThe Stripe webhook endpoint configured by this association.
callback_urlThe callback URL configured at Stripe.
eventsThe deduplicated, sorted event subscriptions configured at Stripe.
configuredtrue indicates successful setup.

The response does not include the signing secret. Retrieve it separately with POST /stripe/webhook-secret using stripe.webhook.read when the application has stripe:webhook:read.

Stripe credentials and provider boundary

The application must have the appropriate Stripe secret API credential configured in Gateman:

  • STRIPE_SECRET_KEY for production live-mode requests;
  • STRIPE_SECRET_KEY_TEST for staging and development test-mode requests.

Gateman contacts the real Stripe API with the selected credential. A missing Stripe API credential, Stripe rejection, or Stripe transport failure is returned as the generic provisioning-unavailable error described below. A Stripe Connect account is not selected or configured by this capability.

Gateman's supported endpoint configuration is not a general Stripe webhook management service. Stripe remains authoritative for provider permissions, endpoint state, and event support.

Idempotency and repeated calls

Three mechanisms must be kept separate:

  1. X-GM-Idempotency-Key is an optional outer Contract v1 header. It is signed, but Write does not maintain a replay ledger or use it to make an invocation free.
  2. Endpoint creation uses Stripe provider-side idempotency for the supported creation request. This is distinct from a Gateman result ledger.
  3. Gateman reconciliation checks the tracked endpoint and compares its callback URL and event list. An unchanged admitted request can make no Stripe mutation, while a changed request updates the tracked endpoint.

Repeated admitted setup calls consume separate Gateman operations. The outer idempotency header does not guarantee an unbilled replay or exactly-once creation across arbitrary failures. Do not blindly retry an uncertain provider mutation.

Operation accounting, limits and concurrency

The operation is billable as stripe.webhook.setup. Each admitted Write invocation consumes one operation, including unchanged configurations and invocations that subsequently fail. Requests rejected before operation admission are not billed. Monthly allowance, concurrency, and accounting availability can prevent admission. Provider and transport errors after admission are not free.

The public retry behavior is:

  • Gateman provider-concurrency rejection: HTTP 429 with Retry-After: 1;
  • provider-concurrency guard unavailable: HTTP 503;
  • setup contention after admission: HTTP 503 with Retry-After: 1;
  • monthly operation allowance exhausted: HTTP 403 with the shared allowance fields;
  • accounting unavailable: HTTP 503.

These Gateman admission responses are distinct from a Stripe HTTP 429. See Operation accounting and Rate limits and concurrency for shared rules.

Errors and responses

Unless noted otherwise, errors are JSON objects with ok: false and an error string. Shared Contract v1 authentication errors and application authorization errors use the status and response contract documented in Authentication and Errors and responses.

ConditionHTTP responsePublic body or handling
Invalid JSON or a non-object JSON body400{"ok":false,"error":"Invalid JSON"}
Unknown request field400{"ok":false,"error":"This endpoint accepts only environment, callback_url and events"}
Missing or unsupported environment400ok: false with the validation error, such as Application environment is required or Unsupported application environment.
Invalid callback URL400ok: false with Invalid callback URL or Callback URL must be a valid HTTPS URL.
Invalid event selection400ok: false with the event validation error.
Contract v1 authentication failure401Shared authentication response; no setup operation is admitted.
Missing stripe:webhook:write authorization401 or 403Shared authorization response; no setup operation is admitted.
Gateman provider-concurrency limit429{"ok":false,"error":"Too many concurrent provider requests"} with Retry-After: 1.
Provider-concurrency guard unavailable503{"ok":false,"error":"Provider concurrency guard unavailable"}.
Monthly operation allowance exhausted403Shared monthly_operation_limit_reached response; no operation is admitted.
Operation accounting unavailable503{"ok":false,"error":"Operation accounting unavailable"}.
Setup already in progress for the same app/mode503{"ok":false,"error":"Stripe webhook setup is already in progress"} with Retry-After: 1. The admitted operation is finalized as failed.
Stripe credential missing, Stripe API rejection, transport failure, missing required stored secret, conflicting tracked state, or local persistence failure502{"ok":false,"error":"Stripe webhook setup unavailable"}. Provider error bodies and storage diagnostics are not passed through.

Do not confuse the Gateman-generated provider-concurrency 429 with a provider-side Stripe 429; provider failures are returned through the generic setup-unavailable contract. A 502 after provider execution is not proof that Stripe did not act, so applications must avoid blind retries and reconcile according to their provider and application policy.

Retrieving and using the signing secret

Use the separate stripe.webhook.read capability at POST /stripe/webhook-secret to retrieve the current application-scoped signing secret. Read requires stripe:webhook:read; Write does not imply it. See the Stripe webhook Read reference for that response and its secret-handling rules.

The receiving application, not Gateman, validates Stripe's signature on the raw webhook request, applies replay and event-deduplication controls, and processes payment or business state. Keep the signing secret out of source, logs, browser code, URLs, and user-visible errors.

Implementation outcome

A correctly integrated application authenticates with Contract v1, has stripe:webhook:write, and calls Write only when it needs to establish or reconcile its configured Stripe callback and event subscriptions. It knows the returned live or test Stripe endpoint identifier and callback URL, can obtain the current signing secret independently if granted Read, and verifies Stripe events in its own webhook handler. A later Write call can reconcile supported callback or event changes without assuming that Gateman receives or processes the resulting deliveries.

Implementation instructions for agents

When implementing an integration:

  1. Use Contract v1 and grant only stripe:webhook:write for endpoint setup; grant stripe:webhook:read separately when secret retrieval is required.
  2. Send only environment, callback_url, and events to POST /stripe/webhook/setup, using production for Stripe live mode and either staging or development for the shared Stripe test mode.
  3. Use an HTTPS callback owned by the receiving application and a nonempty list of Stripe event names. Do not send an endpoint ID, signing secret, or unsupported Stripe management parameter.
  4. Sign the exact request bytes with Contract v1. Do not treat X-GM-Idempotency-Key as a replay ledger or provider idempotency guarantee.
  5. Write may create, update, leave unchanged, or replace the tracked endpoint. A successful response confirms the resulting endpoint configuration; do not infer which action occurred from configured: true, and do not invent an action or status response field.
  6. Protect the signing secret. Retrieve it through Read and verify Stripe signatures in the application's webhook receiver; Gateman does not receive or verify those deliveries.
  7. Account for one stripe.webhook.setup operation per admitted invocation, including unchanged configurations and admitted failures. Follow documented Retry-After behavior and do not blindly retry uncertain provider mutations.
  8. Initiate configuration when required by the application. Do not call Write during every checkout, webhook delivery, page load, or routine request.

Reusable AGENTS.md block

<!-- gateman:begin capability=stripe.webhook.write -->
# Configure a Stripe webhook with Gateman

- Capability: `stripe.webhook.write`
- Scope: `stripe:webhook:write`
- Reference:
  https://docs.getgateman.com/capabilities/stripe.webhook.write/
- Endpoint: `POST /stripe/webhook/setup`
- Operation: `stripe.webhook.setup` (one billable operation per admitted
  invocation)
- Required JSON fields: `environment`, `callback_url`, and `events`; unknown
  fields are rejected.
- `production` uses Stripe live credentials; `staging` and `development` are
  interchangeable labels for one Stripe test-mode association.
- Use an HTTPS callback owned by the receiving application and a nonempty list
  of Stripe event names. Do not submit an endpoint ID, signing secret, or
  unsupported provider parameters.
- Write creates or reconciles the supported tracked endpoint. Creation stores
  and overwrites the selected live/test signing-secret slot; ordinary update
  and unchanged reconciliation preserve the existing stored secret.
- Write does not adopt arbitrary Stripe endpoints, manage multiple endpoints,
  or imply `stripe:webhook:read`, `stripe:use`, or any webhook delivery
  processing.
- Grant `stripe:webhook:read` separately when the application needs
  `POST /stripe/webhook-secret`; protect the returned secret and verify Stripe
  signatures in the application's own webhook receiver.
- Sign the exact request bytes with Contract v1. `X-GM-Idempotency-Key` is not
  a replay ledger or guaranteed unbilled retry. Follow documented
  `Retry-After` responses, account for admitted failures, and do not blindly
  retry uncertain provider mutations.
- Initiate setup only when configuration is required, not during every
  checkout, page load, webhook delivery, or routine request.
<!-- gateman:end capability=stripe.webhook.write -->