Webhooks

Enable real-time event syncing between Recurly Commerce and merchant systems using secure outbound webhooks.

Recurly Commerce webhooks notify your systems the moment something happens — a subscription changes, a billing attempt succeeds or fails, a payment method updates. Every request arrives signed, so you can confirm it actually came from Recurly before you act on it. This page covers setting up webhook access, verifying signatures, and handling deliveries.
Available on all Recurly plans

Prerequisites

  • A webhookSigningKey must 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

Outbound webhooks notify merchants about key events — like subscription changes or billing attempts — by sending signed HTTP requests to a merchant-defined endpoint.

Key benefits

Real-time updates Merchants get immediate notice of critical subscription and billing events.
Secure integration Every request is signed with a merchant-specific key so merchants can confirm it came from Recurly.
Event-driven workflows Merchants can build custom workflows or data syncing based on specific subscription activity.

Key details

Setting up webhook access

1

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.

2

Have the merchant validate incoming requests

Merchants use this key to validate the X-Prive-Hmac-Sha256 header on every request they receive.

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

1

Compute the digest

Calculate an HMAC-SHA256 digest of the raw request body, using the merchant's webhookSigningKey as the HMAC key.

2

Base64-encode it

Encode the resulting digest as base64 — not hex.

3

Compare against the header

Compare your computed value to the X-Prive-Hmac-Sha256 header on the request.

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

TopicDescriptionPayload
subscriptions/createdA new subscription is created in RecurlySubscription payload
subscriptions/line_removedA line item is removed from a subscriptionSubscription payload
subscriptions/status/updatedSubscription status was updatedSubscription payload
orders/createdA new order has been created for a subscriptionOrder payload
billingAttempts/createdA billing attempt was initiatedBilling attempt payload
billingAttempts/successA billing attempt was successfulBilling attempt payload
billingAttempts/failedA billing attempt failedBilling attempt payload
paymentMethod/updatedA customer's payment method was updatedSubscription payload
subscriptionActivities/createdA new activity was logged on a subscriptionActivity payload
subscription/customAttributes/updatedA subscription's custom attributes were updatedCustom 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"
      }
    ]
  }
}
}

Did this page help you?