Build workflows with webhooks
In order to notify your system of asynchronous actions within the Orb platform, Orb provides a powerful, real-time webhooks system that can issue calls to an endpoint of your choosing when certain events take place. Webhooks allow your system or integration to take actions as a result of these events.
Orb's supported webhook events can be useful for a wide variety of use cases:
- Resource lifecycle changes: Set up entitlements in your service as a result of object creation, such as a
subscription.created
event - Usage patterns: Alert internal teams when a specific usage threshold is hit in a billing period, or when Orb detects an anomalous usage pattern for a customer based on historical patterns.
- Balance events: Send out an end-user email when a customer's pre-paid credit balance is nearing zero
Delivery of Resource lifecycle changes webhooks typically occurs in near real-time, though occasional delays may occur.
For alerts on usage, cost, and credit balance, we evaluate alerts (and therefore issue webhooks) several times a day, but the latency guarantee will be a function of your workload. This can be reduced with provisioning for customers on Growth and Enterprise plans.
Webhooks payloads in Orb follow the format:
{
// Unique to this WebhookEvent resource, and can be used for idempotency (process-once) purposes
"id": string,
// ISO8601 `created_at` timestamp of the `WebhookEvent` resource
"created_at": string,
// Identifies the type of webhook event being triggered
"type": string,
// Each webhook is attached to a resource, and the full resource is serialized
// in the webhook body.
"<resource_name>": object,
// Additional properties specific to this event
"properties": object,
}
Configuring webhooks
Webhooks are configured to an HTTPS
URL on your backend server (note that only the HTTPS
scheme is accepted for the production version of your webhook configuration).
Each webhook endpoint is associated with a secret (see the X-Orb-Signature
clause below). By default, all webhook events are sent to every endpoint.
Debugging webhooks
In the Orb UI, you can test your webhook configuration to ensure that it's set up properly and ready to receive events from us. From the webhooks portal, you can select on any webhook endpoint to send a "test event."
If you need to manually trigger a webhook event to be sent to your server, you can also resend it in the UI.
Webhooks retries
Orb guarantees at-least-once delivery for a single webhook event to each of your endpoint.
Orb expects a 2xx
within five seconds as a successful response to a webhook request. If the request returns an error or otherwise times out, Orb will attempt to retry the event on the following retry schedule, stopping when either retries are exhausted or the request is accepted:
- 5 seconds
- 5 minutes
- 30 minutes
- 2 hours
- 5 hours
- 10 hours
- 10 hours
In order for your system to handle duplicate notifications, we recommend storing the id
of a webhook event temporarily in the webhook consumer and refusing to re-process an event if the same id
has already been seen.
Webhook patterns
Event types follow the pattern <resource_name>.<verb>
, where <resource_name>
is the root object that is related to the webhook, and the <verb>
is the event that has taken place (e.g. customer.created
is a newly created Customer resource).
By convention, a key in the payload will match the resource_name
prefix in type
(e.g. if the type
issubscription.action
, the payload will contain the serialized subscription resource under the subscription
key).
Webhook Event Types: Resource Lifecycle Changes
Orb currently issues the following webhooks for resource lifecycle changes:
customer.created
Issued when a customer resource is created.
key | schema |
---|---|
customer | Customer |
properties | {} |
customer.edited
Issued when a customer is edited. The previous_attributes
object will be present and populated with the previous state of any Customer
attributes that have changed.
key | schema |
---|---|
customer | Customer |
properties.previous_attributes.payment_provider | string |
properties.previous_attributes.payment_provider_id | string |
properties.previous_attributes.auto_collection | boolean |
properties.previous_attributes.email | string |
properties.previous_attributes.name | string |
properties.previous_attributes.email_delivery | boolean |
properties.previous_attributes.metadata | object |
properties.previous_attributes.tax_id | TaxId |
properties.previous_attributes.shipping_address | Address |
properties.previous_attributes.billing_address | Address |
subscription.created
Issued when a subscription resource is created.
key | schema |
---|---|
subscription | Subscription |
properties | {} |
subscription.started
Issued when a subscription begins.
key | schema |
---|---|
subscription | Subscription |
properties | {} |
subscription.fixed_fee_quantity_updated
Issued when a subscription's fixed fee quantity has been updated.
key | schema |
---|---|
subscription | Subscription |
properties.old_quantity | number |
properties.new_quantity | number |
properties.effective_date | date-time |
properties.price_id | string |
subscription.edited
Issued when a subscription has been edited. This will not be triggered for changes to pricing.
key | schema |
---|---|
subscription | Subscription |
properties.previous_attributes.metadata | object |
properties.previous_attributes.default_invoice_memo | string |
properties.previous_attributes.auto_collection | boolean |
properties.previous_attributes.net_terms | number |
properties.previous_attributes.invoicing_threshold | number |
subscription.ended
Occurs whenever a customer’s subscription ends/lapses.
key | schema |
---|---|
subscription | Subscription |
properties | {} |
subscription.plan_changed
Issued when a subscription transitions from one plan to a different plan.
key | schema |
---|---|
subscription | Subscription |
properties.previous_plan_id | string |
subscription.plan_version_change_scheduled
Issued when a plan version change is scheduled in the future for a subscription.
key | schema |
---|---|
subscription | Subscription |
properties.effective_date | date-time |
properties.previous_plan_version_number | number |
properties.new_plan_version_number | number |
subscription.plan_version_changed
Issued when a subscription moves to a new plan version.
key | schema |
---|---|
subscription | Subscription |
properties.effective_date | date-time |
properties.previous_plan_version_number | number |
properties.new_plan_version_number | number |
invoice.invoice_date_elapsed
Serializing and consuming this webhook can be very expensive in the presence of a large number of subscriptions and is not enabled by default. Please reach out to the Orb team to opt-in to the webhook or discuss the intended workflow.
Issued when the invoice_date
of an invoice has elapsed.
key | schema |
---|---|
invoice | Invoice |
properties.invoice_date | date-time |
invoice.issued
Issued when an invoice transitions to the "issued"
state. "issued"
invoices are frozen (they cannot be edited, manually or via the API). The automatically_marked_as_paid
property will be true when the issued invoice's amount due is zero, which results in the invoice status being set to paid.
key | schema |
---|---|
invoice | Invoice |
properties.automatically_marked_as_paid | boolean |
invoice.issue_failed
Issued when an invoice fails to issue.
key | schema |
---|---|
invoice | Invoice |
properties.reason | string |
invoice.payment_failed
Issued when automated payment collection for an invoice fails for a configured payment gateway.
key | schema |
---|---|
invoice | Invoice |
properties.payment_provider | "stripe" |
properties.payment_provider_id | string |
properties.payment_provider_transaction_id | string or null |
invoice.payment_processing
Issued when a payment for an invoice starts processing.
key | schema |
---|---|
invoice | Invoice |
properties.payment_provider | "stripe" |
properties.payment_provider_id | string |
invoice.payment_succeeded
Issued when automated payment collection for an invoice succeeds for a configured payment gateway. This webhook is not sent for $0 invoices that are automatically marked as paid upon issuance.
key | schema |
---|---|
invoice | Invoice |
properties.payment_provider | "stripe" |
properties.payment_provider_id | string |
properties.payment_provider_transaction_id | string |
invoice.edited
Issued when a draft invoice has been edited via the webapp. The previous_attributes
object will be present and populated with the previous state of any Invoice
attributes that have changed.
key | schema |
---|---|
invoice | Invoice |
properties.previous_attributes.amount_due | string |
properties.previous_attributes.subtotal | string |
properties.previous_attributes.total | string |
properties.previous_attributes.discounts | array of Discounts |
properties.previous_attributes.minimum | Minimum |
properties.previous_attributes.line_items | array of Invoice Line Items |
invoice.manually_marked_as_void
Issued when an invoice is manually marked as void.
key | schema |
---|---|
invoice | Invoice |
properties | {} |
invoice.manually_marked_as_paid
Issued when an invoice is manually marked as paid.
key | schema |
---|---|
invoice | Invoice |
properties.payment_received_date | date-time |
properties.external_id | string |
properties.notes | string |
invoice.undo_mark_as_paid
Issued when undoing the Paid
status for an invoice that was manually marked as paid.
key | schema |
---|---|
invoice | Invoice |
properties | {} |
invoice.sync_succeded
Issued when an invoice successfully syncs to the invoice provider.
key | schema |
---|---|
invoice | Invoice |
properties.payment_provider | "string" |
properties.payment_provider_id | string |
invoice.sync_failed
Issued when an invoice fails to sync to the invoice provider.
key | schema |
---|---|
invoice | Invoice |
properties.payment_provider | "string" |
properties.payment_provider_id | string |
credit_note.issued
Issued when a credit note is created.
key | schema |
---|---|
credit_note | Credit note |
properties | {} |
credit_note.marked_as_void
Issued when a credit note is marked as void.
key | schema |
---|---|
credit_note | Credit note |
properties | {} |
resource_event.test
Issued via the Orb UI to test if your webhook configuration is set up properly and ready to receive events from us.
key | schema |
---|---|
message | string |
Webhook Event Types: Usage Patterns and Balance Events
Orb currently issues the following usage patterns and balance events webhooks:
subscription.usage_exceeded
Issued when a billable metric in a subscription exceeds a pre-configured quantity threshold.
key | schema |
---|---|
subscription | Subscription |
properties.billable_metric_id | string |
properties.timeframe_start | date-time |
properties.timeframe_end | date-time |
properties.quantity_threshold | number |
subscription.cost_exceeded
Issued when a subscription's accrued spend for a month exceeds a pre-configured currency amount.
Orb will not evaluate alerts on cost that are configured with a threshold below the total of a plan's fixed costs with a prioritized cadence during billing period rollovers. These alerts can be expected to fire near the first actual usage event received for the new billing period and will subsequently be evaluated several times a day. Please note, prepurchased credits are not included in the spend calculation, and only actual spend will be considered.
key | schema |
---|---|
subscription | Subscription |
properties.timeframe_start | date-time |
properties.timeframe_end | date-time |
properties.amount_threshold | number |
customer.credit_balance_depleted
Issued when a customer's prepaid credit balance is depleted.
key | schema |
---|---|
customer | Customer |
properties.pricing_unit.name | string |
properties.pricing_unit.symbol | string |
properties.pricing_unit.display_name | string |
customer.credit_balance_dropped
Issued when a customer's prepaid credit balance is depleted to a configured threshold.
key | schema |
---|---|
customer | Customer |
properties.balance_threshold | string |
properties.pricing_unit.name | string |
properties.pricing_unit.symbol | string |
properties.pricing_unit.display_name | string |
customer.credit_balance_recovered
Issued when a customer's prepaid credit balance is replenished after depletion.
key | schema |
---|---|
customer | Customer |
properties.pricing_unit.name | string |
properties.pricing_unit.symbol | string |
properties.pricing_unit.display_name | string |
Webhooks security
Each webhook event sent by Orb also includes two specific headers in the request:
X-Orb-Timestamp
: This header represents the time that the webhook was sent from Orb. You can check this timestamp and compare it to a configured threshold in your system, in order to prevent processing of the webhook event. This can be a mitigation against replay attacks where webhook events are re-sent, causing downstream systems to behave unexpectedly.X-Orb-Signature
: The signature header (formatted asv1=<signature>
) is a security header which can be used to confirm that the webhook event originated from Orb. We highly recommend verifying the signature, and only processing events where the header matches the signature you generate. Since each webhook endpoint is associated with a secret, your backend server should re-compute the signature by preparing the payload and subsequently computing an HMAC with the SHA 256 function.- The
payload
for the HMAC is the literal"v1:"
followed by the ISO formatX-Orb-Timestamp
and finally the literal:
followed by the event message body itself. - You should use the configured secret for the endpoint URL as the signing secret for the HMAC.
- When comparing to your generated HMAC, you can either prefix the literal
v1=
to compare withX-Orb-Signature
header directly, or extract the element afterv1=
inX-Orb-Signature
to check against your generated value.
- The
Webhooks verification
Orb's SDKs provide verification methods for your webhook events, available in Python, Node, and Go.
Here's an example verification snippet implemented as a Flask endpoint:
from flask import Flask, request
import hmac
from hashlib import sha256
app = Flask(__name__)
# Example Using Flask
@app.route('/webhook-handler', methods=['POST'])
def handle_webhook():
# parse webhook JSON
webhook_event = request.json
if webhook_event is None:
# could not parse request
return ('Unable to parse notification.', 400)
expected_signature = request.headers["X-Orb-Signature"]
iso_format_delivery_time = request.headers["X-Orb-Timestamp"]
secret_string = "<insert your secret string here>"
secret = bytes(secret_string, "utf-8")
# Prefix the beginning of the signature with `v1:<isoformat timestamp>`
prefix = "v1:{0}:".format(iso_format_delivery_time)
# Convert to bytes and append the body of the request
message = prefix.encode("utf-8") + request.data
# Generate an encode message based on the secret and the message string with sha256.
hmac_object = hmac.new(secret, message, sha256)
created_signature = "v1=" + hmac_object.hexdigest()
if not expected_signature == created_signature:
return ('Unable to verify webhook signature.', 400)
# Handle based on webhook_event.type
return ('Successfully processed webhook!', 200)