Skip to main content

subscription.cancelled

An agency cancelled their subscription

Category: Billing

Emitted when: After AgencySubscription.status is set to CANCELLED in app/api/v1/subscriptions/agency.py /agency/cancel route, OR after Square subscription.canceled webhook commits the flip

Envelope

Every delivery wraps the event-specific data block in the canonical envelope documented on the webhook overview page. The body carries SIX keys; the HMAC signature is delivered as the X-Webhook-Signature request header on the inbound POST to your endpoint (NOT as a body field; including it in the body would make the HMAC a function of its own output).

FieldTypeDescription
event_idstringULID; globally unique. Safe to persist as the dedup key.
event_typestringAlways subscription.cancelled for this event.
api_versionstringPayload schema version (YYYY-MM-DD).
timestampintegerUnix epoch seconds at dispatch time.
noncestringULID; unique per delivery, used for replay rejection.
dataobjectEvent-specific payload (see below).

Delivery also sets three HTTP headers on the inbound POST:

HeaderMeaning
X-Webhook-Event-IdMirrors event_id; cheaper than parsing the body to dedup.
X-Webhook-TimestampMirrors timestamp for replay-window checks.
X-Webhook-Signaturesha256=<hex> HMAC over timestamp.body.

Data schema

FieldTypeRequiredDescription
agency_idstringyesAgency Id
subscription_idstringyesSubscription Id
plan_idstringyesPlan Id
statusunknownnoAlways literal 'cancelled'. Present for symmetry.
started_atstringyesStarted At
ends_atstringyesMoment access stops - required, never None. Equals current_period_end for period-end cancels, equals cancelled_at for immediate non-payment cancels.
priceobjectyesPricing tuple for a subscription plan. Locked as a nested sub-object on both subscription.created and subscription.cancelled payloads. Receivers can share a single decoder for the two event shapes. Fields ------ amount: int Price in INTEGER CENTS. Never float. Matches Square v44's money.amount contract. currency: str ISO-4217 code, validated as ^[A-Z]{3}$. interval: Literal["month", "year"] Billing interval. Only monthly and yearly are supported on the platform; a weekly / quarterly subscription would be a new event type, not a shape drift here.
cancelled_atstringyesCancelled At
reasonstring | nullnoFree-form reason from the dashboard cancel form. None when Square initiated the cancel (non-payment, card expired).

Sample payload

The sample below is a live fixture. Feed the exact body bytes shown plus the timestamp into the HMAC-SHA256 recipe from the webhook overview using the documented test-vector secret (test_secret_001 ) and you will recover the signature header value printed below.

Body (delivered as the HTTP request body):

{
"event_id": "evt_3QJE7VS6Z03RSX83EZ4E7QQBV7",
"event_type": "subscription.cancelled",
"api_version": "2026-04-17",
"timestamp": 1745339401,
"nonce": "0XF7P6F60D7R67DSC2D27ZHGZ6",
"data": {
"agency_id": "user_01HXAGENCY0000000000000",
"subscription_id": "sub_01HXSUB0000000000000000",
"plan_id": "01HX5Y7Z2M3N4P5Q6R7S8T9U0V",
"status": null,
"started_at": "2026-05-29T12:00:00Z",
"ends_at": "2026-05-29T12:00:00Z",
"price": {
"amount": 0,
"currency": "USD",
"interval": "month"
},
"cancelled_at": "2026-05-29T12:00:00Z",
"reason": "agency_request"
}
}

Signature header (delivered as X-Webhook-Signature on the same request):

sha256=52ffd5798f7f4bb4d1d3030da6a4c53f4da6645fbb19e2a870acf95672cfb817

Retry behaviour

Failed deliveries retry on the sequence 2, 4, 8, 16, 32 seconds (five attempts plus the initial call for a total of six over approximately 62 seconds of wall clock). After the final attempt the delivery lands in the dead-letter queue and the dashboard operator can replay it from Settings > API Keys > Webhook Endpoint > Deliveries. See the webhook overview for the full rules.

Verification

Every receiver MUST verify the X-Webhook-Signature header using the recipe in the webhook overview. Rejecting deliveries whose timestamp is more than 5 minutes off your wall clock (after NTP correction on your side) or whose nonce has already been consumed in the last 10 minutes is part of the contract.