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 label | Stripe mode | Stripe API credential | Webhook secret slot |
|---|---|---|---|
production | Live | STRIPE_SECRET_KEY | STRIPE_WEBHOOK_SECRET |
staging | Test | STRIPE_SECRET_KEY_TEST | STRIPE_WEBHOOK_SECRET_TEST |
development | Test | STRIPE_SECRET_KEY_TEST | STRIPE_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
httpsscheme; - 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_SECRETfor live/production;STRIPE_WEBHOOK_SECRET_TESTfor 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:
| Field | Meaning |
|---|---|
ok | The setup operation completed successfully. |
environment | The accepted, normalized public environment label supplied by the caller. |
endpoint_id | The Stripe webhook endpoint configured by this association. |
callback_url | The callback URL configured at Stripe. |
events | The deduplicated, sorted event subscriptions configured at Stripe. |
configured | true 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_KEYforproductionlive-mode requests;STRIPE_SECRET_KEY_TESTforstaginganddevelopmenttest-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:
X-GM-Idempotency-Keyis 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.- Endpoint creation uses Stripe provider-side idempotency for the supported creation request. This is distinct from a Gateman result ledger.
- 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
429withRetry-After: 1; - provider-concurrency guard unavailable: HTTP
503; - setup contention after admission: HTTP
503withRetry-After: 1; - monthly operation allowance exhausted: HTTP
403with 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.
| Condition | HTTP response | Public body or handling |
|---|---|---|
| Invalid JSON or a non-object JSON body | 400 | {"ok":false,"error":"Invalid JSON"} |
| Unknown request field | 400 | {"ok":false,"error":"This endpoint accepts only environment, callback_url and events"} |
| Missing or unsupported environment | 400 | ok: false with the validation error, such as Application environment is required or Unsupported application environment. |
| Invalid callback URL | 400 | ok: false with Invalid callback URL or Callback URL must be a valid HTTPS URL. |
| Invalid event selection | 400 | ok: false with the event validation error. |
| Contract v1 authentication failure | 401 | Shared authentication response; no setup operation is admitted. |
Missing stripe:webhook:write authorization | 401 or 403 | Shared authorization response; no setup operation is admitted. |
| Gateman provider-concurrency limit | 429 | {"ok":false,"error":"Too many concurrent provider requests"} with Retry-After: 1. |
| Provider-concurrency guard unavailable | 503 | {"ok":false,"error":"Provider concurrency guard unavailable"}. |
| Monthly operation allowance exhausted | 403 | Shared monthly_operation_limit_reached response; no operation is admitted. |
| Operation accounting unavailable | 503 | {"ok":false,"error":"Operation accounting unavailable"}. |
| Setup already in progress for the same app/mode | 503 | {"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 failure | 502 | {"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:
- Use Contract v1 and grant only
stripe:webhook:writefor endpoint setup; grantstripe:webhook:readseparately when secret retrieval is required. - Send only
environment,callback_url, andeventstoPOST /stripe/webhook/setup, usingproductionfor Stripe live mode and eitherstagingordevelopmentfor the shared Stripe test mode. - 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.
- Sign the exact request bytes with Contract v1. Do not treat
X-GM-Idempotency-Keyas a replay ledger or provider idempotency guarantee. - 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. - 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.
- Account for one
stripe.webhook.setupoperation per admitted invocation, including unchanged configurations and admitted failures. Follow documentedRetry-Afterbehavior and do not blindly retry uncertain provider mutations. - 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 -->
