Webhooks
Enable real-time event syncing between Recurly Commerce and merchant systems using secure outbound webhooks.
Prerequisites
- A
webhookSigningKeymust be generated and stored for each merchant before Recurly will deliver events to their endpoint. - Merchants can't generate this key themselves — request one from Recurly Support at [email protected].
- Merchants must verify webhook request signatures using the shared key.
Limitations
- There's no key rotation procedure. If a merchant needs a new key, it's a hard cutover: signatures generated with the old key stop validating once the new key takes effect, with no overlap window.
Definition
Key benefits
Key details
Setting up webhook access
Request a webhookSigningKey
Merchants can't generate this key themselves. Email [email protected] to have Recurly Support issue one — it's shared securely and used to sign every webhook request sent to the merchant's endpoint.
A signing key isn't required to create the subscription itself — a POST to our public API succeeds and returns 201 without one. Recurly won't send any deliveries to that endpoint until a signing key is configured for the merchant, though. The key gates delivery, not registration.
Verifying webhook signatures
The signature covers the raw body only — there's no timestamp in the signed payload, and no replay window to enforce as a result. For replay protection, dedupe on the idempotencyKey in the payload instead of a timestamp check.
Handling deliveries
Recurly considers a delivery successful when your endpoint responds with a 2xx status code within 5 seconds. Any other response — an error status, a timeout, or no response at all — is treated as a failed delivery.
Every payload includes an idempotencyKey. Use it to detect duplicate deliveries and process events idempotently.
Registration status
A webhook registration is either active or removed — there's no separate "paused" or "failing" state today. Repeated delivery failures don't disable a registration; it stays active until you explicitly delete it.
Testing webhooks
Use your testing store on Shopify to send and receive test webhook events.
Webhook topics
| Topic | Description | Payload |
subscriptions/created | A new subscription is created in Recurly | Subscription payload |
subscriptions/line_removed | A line item is removed from a subscription | Subscription payload |
subscriptions/status/updated | Subscription status was updated | Subscription payload |
orders/created | A new order has been created for a subscription | Order payload |
billingAttempts/created | A billing attempt was initiated | Billing attempt payload |
billingAttempts/success | A billing attempt was successful | Billing attempt payload |
billingAttempts/failed | A billing attempt failed | Billing attempt payload |
paymentMethod/updated | A customer's payment method was updated | Subscription payload |
subscriptionActivities/created | A new activity was logged on a subscription | Activity payload |
subscription/customAttributes/updated | A subscription's custom attributes were updated | Custom attributes payload |
Payload structure
Webhook payloads follow this general structure:
{
"topic": "webhook_topic",
"idempotencyKey": "123",
"data": { /* event-specific data */ }
}Example: subscriptions/created payload
{
"topic": "subscriptions/created",
"idempotencyKey": "<idempotency_key>",
"data": {
"id": "subscriptionContract.id",
"createdAt": "subscriptionContract.createdAt",
"updatedAt": "subscriptionContract.updatedAt",
"friendlyId": "subscriptionContract.friendlyId",
"externalId": "subscriptionContract.internalId",
"subscriber": {
"id": "shopper.id",
"createdAt": "shopper.createdAt",
"updatedAt": "shopper.updatedAt",
"externalId": "merchantShopperRelationShipInternalId || shopper.providerId",
"firstName": "shopper.firstName",
"lastName": "shopper.lastName",
"email": "shopper.email",
"phoneCountryCode": "shopper.phoneCountryCode",
"phone": "shopper.phone"
},
"purchaseDate": "subscriptionContract.purchaseDate",
"cancelDate": "subscriptionContract.cancelDate",
"cancelReason": "subscriptionContract.cancelReason",
"pausedAtDate": "subscriptionContract.pausedAtDate",
"nextDeliveryDate": "subscriptionContract.adjustedNextDeliveryDate",
"nextBillingDate": "subscriptionContract.adjustedNextBillingDate",
"status": "subscriptionContract.status",
"deliveryCadenceCount": "subscriptionContract.deliveryCadenceCount",
"deliveryCadenceUnit": "subscriptionContract.deliveryCadenceUnit",
"billingCadenceCount": "subscriptionContract.billingCadenceCount",
"billingCadenceUnit": "subscriptionContract.billingCadenceUnit",
"isPrepaid": "subscriptionContract.isPrepaid",
"isMembership": "subscriptionContract.isMembership",
"currencyCode": "subscriptionContract.currencyCode",
"deliveryFirstName": "subscriptionContract.deliveryFirstName",
"deliveryLastName": "subscriptionContract.deliveryLastName",
"deliveryAddress1": "subscriptionContract.deliveryAddress1",
"deliveryAddress2": "subscriptionContract.deliveryAddress2",
"deliveryCountry": "subscriptionContract.deliveryCountry",
"deliveryProvince": "subscriptionContract.deliveryProvince",
"deliveryCity": "subscriptionContract.deliveryCity",
"deliveryZip": "subscriptionContract.deliveryZip",
"deliveryShippingOption": "subscriptionContract.deliveryShippingOption",
"deliveryPrice": "subscriptionContract.deliveryPrice",
"deliveryPhone": "subscriptionContract.deliveryPhone",
"deliveryCompany": "subscriptionContract.deliveryCompany",
"billingFirstName": "subscriptionContract.billingFirstName",
"billingLastName": "subscriptionContract.billingLastName",
"billingAddress1": "subscriptionContract.billingAddress1",
"billingAddress2": "subscriptionContract.billingAddress2",
"billingCountry": "subscriptionContract.billingCountry",
"billingProvince": "subscriptionContract.billingProvince",
"billingCity": "subscriptionContract.billingCity",
"billingZip": "subscriptionContract.billingZip",
"paymentMethodExternalId": "subscriptionContract.creditCardInternalId",
"paymentMethodBrand": "subscriptionContract.creditCardBrand",
"paymentMethodEmail": "subscriptionContract.paymentMethodEmail",
"paymentMethodExpiryMonth": "subscriptionContract.creditCardExpiryMonth",
"paymentMethodExpiryYear": "subscriptionContract.creditCardExpiryYear",
"paymentMethodType": "subscriptionContract.paymentMethodType",
"paymentMethodLast4Digits": "subscriptionContract.creditCardLastDigits",
"paymentMethodName": "subscriptionContract.creditCardName",
"pauseLimitSettings": "subscriptionContract.pauseLimitSettings",
"lines": [
{
"id": "line.id",
"createdAt": "line.createdAt",
"updatedAt": "line.updatedAt",
"externalId": "line.internalId",
"subscriptionId": "line.subscriptionContractId",
"variant": {
"id": "variant.id",
"externalId": "variant.providerVariantId",
"status": "variant.status",
"currentPrice": "variant.currentPrice",
"variantTitle": "variant.variantTitle",
"sku": "variant.sku",
"product": {
"id": "product.id",
"createdAt": "product.createdAt",
"updatedAt": "product.updatedAt",
"externalId": "product.providerProductId",
"imageUrl": "product.imageUrl",
"productTitle": "product.productTitle",
"description": "product.description"
}
},
"quantity": "line.quantity",
"currentPrice": "line.currentPrice",
"subscriptionOfferOptionId": "line.subscriptionOfferOption.id",
"sellingPlanId": "line.subscriptionOfferOption.sellingPlanId",
"schedule": {
"id": "schedule.id",
"deliveryInterval": "schedule.deliveryInterval",
"deliveryStartOffset": "schedule.deliveryStartOffset",
"lastIncludedOrderNumber": "schedule.lastIncludedOrderNumber",
"lastIncludedDate": "schedule.lastIncludedDate",
"createdAt": "schedule.createdAt",
"updatedAt": "schedule.updatedAt"
},
"customAttributes": "line.customAttributes"
}
],
"upcomingOrders": [
{
"scheduledDate": "uo.scheduledDate",
"isSkipped": "uo.isSkipped",
"lines": [
{
"id": "line.id",
"createdAt": "line.createdAt",
"updatedAt": "line.updatedAt",
"externalId": "line.internalId",
"subscriptionId": "line.subscriptionContractId",
"variant": {
"id": "variant.id",
"externalId": "variant.providerVariantId",
"status": "variant.status",
"currentPrice": "variant.currentPrice",
"variantTitle": "variant.variantTitle",
"sku": "variant.sku",
"product": {
"id": "product.id",
"createdAt": "product.createdAt",
"updatedAt": "product.updatedAt",
"externalId": "product.providerProductId",
"imageUrl": "product.imageUrl",
"productTitle": "product.productTitle",
"description": "product.description"
}
},
"quantity": "line.quantity",
"currentPrice": "line.currentPrice",
"subscriptionOfferOptionId": "line.subscriptionOfferOption.id",
"sellingPlanId": "line.subscriptionOfferOption.sellingPlanId",
"schedule": {
"id": "schedule.id",
"deliveryInterval": "schedule.deliveryInterval",
"deliveryStartOffset": "schedule.deliveryStartOffset",
"lastIncludedOrderNumber": "schedule.lastIncludedOrderNumber",
"lastIncludedDate": "schedule.lastIncludedDate",
"createdAt": "schedule.createdAt",
"updatedAt": "schedule.updatedAt"
},
"customAttributes": "line.customAttributes"
}
],
"oneTimeAddOns": [
{
"id": "o.id",
"variantId": "o.variant.id",
"quantity": "o.quantity"
}
]
}
],
"customAttributes": "subscriptionContract.customAttributes",
"gift": {
"id": "gift.id",
"createdAt": "gift.createdAt",
"updatedAt": "gift.updatedAt",
"numberOfDeliveries": "gift.numberOfDeliveries",
"to": "gift.to",
"email": "gift.email",
"message": "gift.message",
"isOpened": "gift.isOpened",
"isPrepaid": "gift.isPrepaid"
},
"recipient": {
"id": "shopper.id",
"createdAt": "shopper.createdAt",
"updatedAt": "shopper.updatedAt",
"externalId": "merchantShopperRelationShipInternalId || shopper.providerId",
"firstName": "shopper.firstName",
"lastName": "shopper.lastName",
"email": "shopper.email",
"phoneCountryCode": "shopper.phoneCountryCode",
"phone": "shopper.phone"
},
"discountCodes": [
{
"id": "subscriptionContractDiscount.id",
"createdAt": "subscriptionContractDiscount.createdAt",
"updatedAt": "subscriptionContractDiscount.updatedAt",
"deletedAt": "subscriptionContractDiscount.deletedAt",
"subscriptionId": "subscriptionContractDiscount.subscriptionContractId",
"code": "subscriptionContractDiscount.title",
"discountCodeExternalId": "subscriptionContractDiscount.providerId",
"type": "subscriptionContractDiscount.type",
"targetType": "subscriptionContractDiscount.targetType",
"discountModifier": "subscriptionContractDiscount.discountModifier",
"discount": "subscriptionContractDiscount.discount",
"recurringCycleLimit": "subscriptionContractDiscount.recurringCycleLimit",
"usageCount": "subscriptionContractDiscount.usageCount"
}
],
"dunningInstances": [
{
"createdAt": "dunningContract.createdAt",
"dunningComplete": "dunningContract.dunningComplete",
"lastRetryDate": "dunningContract.lastRetryDate",
"billingRetryCount": "dunningContract.billingRetryCount",
"maxBillingRetryCount": "dunningContract.maxBillingRetryCount",
"errorCode": "dunningContract.errorCode",
"isReactivated": "dunningContract.isReactivated",
"contractCancelledAt": "dunningContract.contractCancelledAt",
"maxBillingRetryCountAction": "dunningContract.maxBillingRetryCountAction"
}
]
}
}
}Updated 7 days ago