project.payment_method.updated
A payment gateway's credentials, link state or on/off switch changes.
This payload never carries a credential. When gateway keys are rotated,
changes.config.keys names which keys changed — ["clientId"],
["secret"] — and nothing else. Neither the old nor the new value is sent,
and the payment_method snapshot has no config at all.
When this fires
A payment method on the project changes and at least one of active, linked or a key inside its stored config is different afterwards. The surfaces that raise it:
- Dashboard — the payment method editor on the project's Payment Methods page (credentials and switches), and the on/off toggle on each gateway row.
- Telegram — the main bot's payment-method wizard, when a creator walks through connecting a gateway that the project already has in that mode. The wizard re-saves the row through the same path as the dashboard.
- REST —
POST /v1/projects/{project}/payment-methods/{method}/activateand/deactivate. There is noPATCHfor payment methods, so credentials cannot be rotated over REST. - MCP — the
activate_payment_methodanddeactivate_payment_methodtools.
It does not fire when:
- Nothing changed. Activating a gateway that is already on, or re-saving identical credentials, writes nothing and emits nothing.
- A Stripe method is switched on before its Connect account is attached. The call is refused, the row is untouched and there is no event.
- Stripe Connect onboarding completes or is disconnected. Those flows write the row directly and raise no event, even though they change
linkedandactive. - A gateway is first set up. There is no
project.payment_method.createdevent; the first thing you hear about a new gateway is its first update or its deletion. - The gateway is removed. That is
project.payment_method.deleted.
Required ability
project-payment-method:view — token must carry this at mint time to subscribe an endpoint to this event.
Payload
{
"id": "evt_01HX...",
"type": "project.payment_method.updated",
"created_at": "2026-09-06T09:20:00Z",
"api_version": "2026-05-01",
"project_id": "7f3d1c92-8b45-4e6a-9d21-5c8e0a4b6f13",
"data": {
"payment_method": {
"id": "a15d70c8-3e46-4b92-b70f-58c9d2140e63",
"provider": "paypal",
"mode": "live",
"active": true
},
"changes": {
"config": {
"keys": ["clientId"]
}
}
}
}A switch flip looks like every other changes map in the catalog:
{
"id": "evt_01HX...",
"type": "project.payment_method.updated",
"created_at": "2026-09-06T09:21:00Z",
"api_version": "2026-05-01",
"project_id": "7f3d1c92-8b45-4e6a-9d21-5c8e0a4b6f13",
"data": {
"payment_method": {
"id": "a15d70c8-3e46-4b92-b70f-58c9d2140e63",
"provider": "paypal",
"mode": "live",
"active": false
},
"changes": {
"active": { "from": true, "to": false }
}
}
}Field reference
| Field | Type | Notes |
|---|---|---|
id | string ULID | Unique event id, prefixed evt_. Use for idempotent processing. |
type | string | Always project.payment_method.updated for this event. |
created_at | ISO 8601 timestamp | Server-side emission time. |
api_version | string | Webhook API contract version. |
project_id | string UUID | Project this event belongs to. |
data.payment_method | object | Post-update snapshot — id, provider, mode, active only. Never config, never linked. |
data.payment_method.id | string UUID | Payment method identifier. |
data.payment_method.provider | string | A gateway slug (stripe, paypal, skrill, coinpayments, paystack, razorpay, ceypay, accesscode) or a connector:provider key for a currency a connector brings (telegram:stars); platformcurrency is the legacy alias earlier native rows carried. |
data.payment_method.mode | enum: live, test | Which of the gateway's environments this row holds. A project has at most one row per provider per mode. |
data.payment_method.active | boolean | Whether the gateway is offered to new buyers after the change. |
data.changes | object | What changed. One or more of active, linked, config. Never empty — an empty map means no event. |
data.changes.active.from | boolean | Previous switch position. Present only when the switch flipped. |
data.changes.active.to | boolean | New switch position. |
data.changes.linked.from | boolean | Previous link state. Present only when it changed. |
data.changes.linked.to | boolean | New link state. |
data.changes.config.keys | array of strings | The gateway credential keys whose value changed or was newly stored, e.g. ["clientId", "secret"]. Present only when at least one did. Values are never sent. |
Caveats
linkedcan appear inchangesbut is not in the snapshot. Read the new value fromchanges.linked.to, or fromGET /v1/projects/{project}/payment-methods/{method}, which does carrylinked.modenever appears inchanges. A row's mode is fixed — live and test are separate rows — so switching a gateway from test to live is a different method with a differentid, not an update to this one.- Every update re-queues a sync of the project's plans to that gateway, so a
plan.sync_completedper active plan usually follows within a minute. Rotated credentials may point at a different gateway account whose catalogue has never seen the plans. config.keysare the gateway's own field names as Subscriby stores them (clientId,secret,publicKey, …), not a normalised list. Stored config is merged on save, so a key is reported when its value changed or was added — keys are never removed through an update.- Switching a gateway off does not affect subscriptions already sold through it; they keep renewing. It only stops new buyers being offered that gateway.
Related events
project.payment_method.deleted— the gateway is removed from the project.plan.sync_completed— the catalogue re-push that follows each update.- Project events overview — back to family overview.
How is this guide?