Naming webhook events consistently
Event names are the first thing developers see when working with your webhooks. A clear naming convention helps them understand what happened without reading documentation, route events to the right handlers, and predict names for events they have not seen yet. A confusing convention creates friction every time someone integrates with your system.
This article covers the patterns that work well, the mistakes to avoid, and how to evolve your naming scheme as your product grows.
The resource.action pattern
The most widely adopted convention is resource.action, where the resource is a noun describing what changed and the action is a verb describing what happened to it. Stripe uses payment_intent.succeeded, GitHub uses pull_request.opened, and Shopify uses orders/create.
This pattern works because it mirrors how developers think about APIs. If your REST API has /users and /orders endpoints, your webhooks should have user.* and order.* events. The mental model transfers directly.
Keep resource names singular rather than plural. Use customer.created not customers.created. This matches typical API conventions and reads more naturally since each event describes one thing happening to one resource.
Use past tense for completed actions: created, updated, deleted, completed, failed. Present tense works for ongoing states: pending, processing. Avoid future tense since webhooks describe things that happened, not things that will happen.
# Good examples
customer.created
order.completed
payment.failed
subscription.canceled
invoice.payment_succeeded
# Avoid
customers.create # plural, imperative
order.will_complete # future tense
new_payment # no clear resource/action split
Handling nested resources
Some events involve resources that belong to other resources. An invoice line item belongs to an invoice, which belongs to a customer. You have several options for naming these events.
The simplest approach is to name events after the immediate resource: invoice_item.created. This keeps names short but loses context about the parent.
A more explicit approach includes the parent: invoice.item.created or customer.invoice.created. This provides context but creates longer names that may become unwieldy with deep nesting.
Stripe takes a hybrid approach, using compound resource names like payment_intent.amount_capturable_updated. The resource is payment_intent and the action describes what specifically changed.
Pick one approach and apply it consistently. Inconsistency is worse than any particular choice. If you use invoice.item.created, do not also use customer_subscription_updated elsewhere.
Naming state transitions
Many resources move through states: orders go from pending to paid to shipped to delivered, subscriptions go from trial to active to canceled. You can model these as generic updates or as specific state transitions.
Generic updates use a single event type with the new state in the payload: order.updated with status: "shipped" in the data. This minimizes the number of event types but requires consumers to inspect the payload to know what changed.
Specific transitions use distinct event types for each state: order.shipped, order.delivered, subscription.activated, subscription.canceled. This lets consumers subscribe to exactly the events they care about without filtering.
Most mature webhook systems use specific transitions for important state changes and generic updates for less significant changes. A payment failing deserves payment.failed, but a customer changing their display name can be customer.updated.
Versioning event names
As your product evolves, you will need to add new events. Sometimes you will want to change existing events in incompatible ways. Your naming convention should accommodate this.
For new events, simply add them following your existing pattern. Existing subscribers ignore events they do not recognize, so new events are backward compatible.
For incompatible changes, you have options. You can create a new event with a different name: if order.completed needs to change significantly, introduce order.finalized and deprecate the old one. Alternatively, version the entire webhook API so that v1 subscribers get old formats and v2 subscribers get new ones.
Avoid putting version numbers in event names like order.completed.v2. This pollutes your namespace and makes it harder for consumers to write clean event routing code. Version at the subscription or API level instead.
Common naming mistakes
Avoid abbreviations that save a few characters but hurt readability. Use customer.subscription.created not cust.sub.crtd. The extra bytes are negligible compared to the cognitive load of decoding abbreviations.
Do not include implementation details in event names. database.row.inserted exposes internals that might change. customer.created describes the business event regardless of how you store data.
Be careful with synonyms. If you use deleted for some resources and removed for others, developers will constantly check documentation. Pick one term and use it everywhere.
Avoid overly generic names. entity.modified tells consumers nothing useful. They need to parse the payload to understand what happened, defeating the purpose of descriptive event names.
Documenting your events
Whatever convention you choose, document it explicitly. List every event type with a description of when it fires and what data it includes. Provide example payloads for each event.
Group events by resource in your documentation. Developers looking for order-related events should find them together, not scattered alphabetically between invoice.* and payment.*.
Note which events are triggered by user actions versus system processes. A subscription.canceled might fire when a customer cancels or when your system cancels for non-payment. The payload should distinguish these cases, and your documentation should explain the difference.