Subscription Status Webhooks

Receive a webhook each time a subscription changes status.

Each time a subscription moves from one status to another, Acquired sends a billing.subscription.status_changed webhook. This lets your system keep its own view of a subscriber's access in step with Acquired Billing, for example suspending access when a subscription becomes past_due and restoring it when it returns to active.

The webhook uses the standard Webhook-Version 2 envelope. For webhook headers and how to validate the integrity of a webhook using its hash, refer to the webhook structure section of our Webhook Notifications guide.

📘

Note

While webhooks provide real-time updates, they can occasionally be interrupted or missed. We recommend also using GET /subscriptions/{subscription_id} as a fallback to reliably confirm a subscription's current status.


When is the webhook sent?

A webhook is sent for every status change after the subscription has been created, including:

  • Activation — an incomplete subscription is activated and becomes active, or trialing when a trial_end was set.
  • Trial ending — a trialing subscription takes its first payment and becomes active.
  • Failed payments and recovery — a subscription becomes past_due after a failed payment, and returns to active once it is recovered. See how failed payments affect the subscription lifecycle.
  • Pausing — a subscription is paused via the API or the Hub.
  • Cancellation — a subscription is cancelled via the API or the Hub, reaches its billing cycle limit, or is cancelled automatically after repeated payment failures.
📘

No webhook at creation

Creating a subscription does not send a webhook, whatever status it is created in (incomplete, active or trialing). Use the status returned in the Create Subscription response as the starting point; webhooks report every change from there.

Scheduling a cancellation with ?at_period_end=true does not change the status, so no webhook is sent until the subscription is actually cancelled at the end of the period.


Webhook body

FieldTypeDescription
subscription_idstringULID of the subscription whose status changed.
customer_idstringUUID of the customer the subscription belongs to.
old_statusstringThe status before the change.
new_statusstringThe status after the change.
changed_atstringWhen the status changed, as an ISO 8601 date-time with a UTC offset.

old_status and new_status are one of incomplete, trialing, active, past_due, paused or cancelled. See Subscription statuses for what each status means.

📘

The body may grow

Acquired may add new optional fields to webhook_body without changing the webhook type. Ignore fields you don't recognise, and always calculate the hash over the complete payload as received.


Examples

Subscription activated — an incomplete subscription has its payment method added:

{
  "webhook_type": "billing.subscription.status_changed",
  "webhook_id": "3f9c2e1a-7b4d-4c8e-9a1f-2d6b8e0c5a71",
  "timestamp": 1791290763,
  "webhook_body": {
    "subscription_id": "01KV7YAB5GMD32H2A2DHZ6FKZ7",
    "customer_id": "c2b8f0d4-5e6a-4f1b-8c3d-9a7e2f1b0c64",
    "old_status": "incomplete",
    "new_status": "active",
    "changed_at": "2026-10-06T12:46:02.5734211+00:00"
  }
}

Payment failed — a bill payment was declined and the subscription became past_due:

{
  "webhook_type": "billing.subscription.status_changed",
  "webhook_id": "8a1d4b6e-2c3f-4e5a-b7d9-0f1e2a3b4c5d",
  "timestamp": 1793969163,
  "webhook_body": {
    "subscription_id": "01KV7YAB5GMD32H2A2DHZ6FKZ7",
    "customer_id": "c2b8f0d4-5e6a-4f1b-8c3d-9a7e2f1b0c64",
    "old_status": "active",
    "new_status": "past_due",
    "changed_at": "2026-11-06T12:46:01.1029384+00:00"
  }
}

Where the webhook is sent

  • The subscription's webhook_url, when one is set. It can be supplied when you create a subscription, or when activating via Components it is persisted from the webhook_url on the payment session. This is the same URL that receives Bill Payment Webhooks.
  • Otherwise, your webhook endpoint configured in the Hub for subscription status changes.

Handling the webhook

Respond quickly with a 2xx. Any 2xx response marks the webhook as delivered. Acquired waits up to 10 seconds for a response; a timeout, a connection failure or any other status code counts as a failed attempt.

Expect retries, and deduplicate on webhook_id. A failed attempt is retried, with increasing and randomised gaps between attempts, for up to 10 attempts in total. Every retry carries the same webhook_id, so if you receive a webhook_id you have already processed, acknowledge it with a 2xx and take no further action.

Order by changed_at, not by arrival. Because of retries, webhooks for the same subscription can arrive out of order. Compare changed_at with the last change you recorded for that subscription_id, and ignore any webhook older than it.

🚧

Persistently failing endpoints

If your endpoint fails every delivery over an extended period, Acquired may stop sending webhooks to it until it is re-enabled. Contact support if you stop receiving webhooks after an outage on your side.


Did this page help you?