Link
✎

Recurly Licensing Integration

In short: Recurly sends webhook notifications as JSON (with an HMAC-SHA256 signature) or XML. Your receiver creates a license for a new subscription and one for each renewal, and deactivates it when the subscription expires.

Recurly sends webhook notifications for accounts, subscriptions and payments. You set up endpoints in the Recurly admin interface, up to 10 per site. Webhooks are available on all Recurly plans.

A Gateway connector for Recurly was requested (issue #3) but does not exist. The approach below needs no Gateway.

What Recurly sends

Recurly sends an HTTP POST in JSON or XML, whichever format you choose for the endpoint. JSON notifications carry id, object_type, site_id, event_type and event_time, plus the subscription uuid or the account account_code.

Map Recurly events to NetLicensing

Recurly event What to do in NetLicensing
new_account_notification Create a licensee
new_subscription_notification Create a license from the plan’s template
renewed_subscription_notification Create an additional license for the next period
successful_payment_notification Payment succeeded. The subscription notifications already cover the periods, so do not add a license here as well
updated_subscription_notification Upgrade, downgrade or renewal date change: create a license from the new template and deactivate the old one
canceled_subscription_notification The subscription stays valid until expires_at: deactivate the license at that date, or when expired_subscription_notification arrives
expired_subscription_notification Deactivate the license (active=false)
failed_payment_notification Your choice: keep access during the platform’s retry period, or deactivate

Recurly documents no named past-due notification, and calls the payment notifications “legacy”, suggesting subscription and invoice notifications instead.

Check that the request comes from Recurly

Choose JSON for the endpoint: only JSON notifications are signed (XML notifications are not). The header recurly-signature has the form <timestamp in milliseconds>,<hex signature>[,<hex signature>]. The signature is an HMAC-SHA256 over the timestamp, a dot and the raw body, keyed with the endpoint’s own key. You can add HTTP Basic Auth on the Webhook Endpoints page.

Retries and duplicates

Answer with 2xx within 5 seconds; redirects are not followed. Recurly makes up to 10 attempts, with the interval 10 + x * 2^(x + 5) seconds, and keeps notifications for 15 days. The header recurly-notification-id stays the same across retries, so use it to deduplicate. Expect duplicates and out-of-order delivery.

Match the customer and the plan

The JSON samples are minimal: they do not include the account email or the plan. XML notifications include <account_code>, <email>, the subscription <uuid> and <plan_code>. If you use JSON, take the missing details from the Recurly API. Recurly custom fields exist on accounts, charges, items, plans and subscriptions (text, up to 255 characters, defined in the admin console; the number allowed depends on your Recurly plan). The documentation does not say whether they appear in webhook payloads, so a lookup table from plan_code to licenseTemplateNumber is the safe route.

Whatever ids you pick, use them as the NetLicensing numbers so later events can address the licensee and license directly and retries cannot create duplicates; see Use the platform’s IDs as NetLicensing numbers.

Example: the calls

Recurly                     Your receiver                 NetLicensing
|                           |                             |
| subscription renewed ---->|                             |
|                           | verify signature            |
|< GET account (API) -------|                             |
|                           | POST /licensee ------------>|
|                           |  number=account code        |
|                           | POST /license ------------->|
|                           |  number=uuid-period         |
|< 2xx ---------------------|                             |
|                           |                             |

The values below are illustrative; field names follow the vendor documentation listed under Sources. Form-encoded bodies are shown decoded for readability. Code samples are Python, using only the standard library unless noted.

1. Recurly calls your receiver

POST /webhooks/recurly HTTP/1.1
Content-Type: application/json
recurly-signature: 1731052800000,<hex signature>

{
  "id": "ra8foq26o2dt",
  "object_type": "subscription",
  "site_id": "qc326l1hl8k9",
  "event_type": "renewed",
  "event_time": "2022-07-26T22:01:56Z",
  "uuid": "63ab531e1d5b1d47eaf1ef44eeb853c3"
}

2. Your receiver checks the request

import hashlib, hmac

def verify_recurly(raw_body: bytes, header: str, endpoint_key: str) -> bool:
    # header = request.headers["recurly-signature"]: "<ms timestamp>,<sig>[,<sig>]"
    timestamp, *signatures = header.split(",")
    signed = timestamp.encode() + b"." + raw_body
    expected = hmac.new(endpoint_key.encode(), signed, hashlib.sha256).hexdigest()
    return any(hmac.compare_digest(expected, s) for s in signatures)

Verify the raw request body exactly as received, before any JSON or form parsing. Only JSON endpoints are signed. The minimal JSON notification has no email or plan, so read the account and subscription through the Recurly API before you create the license.

3. Your receiver calls NetLicensing

NLIC=https://go.netlicensing.io/core/v2/rest
AUTH="apiKey:$NETLICENSING_API_KEY"    # API key with ROLE_APIKEY_OPERATION

# New account: the Recurly account code becomes the licensee number
curl --user $AUTH --header "Accept: application/json" \
  --data "productNumber=PQVJQ5F7H&number=acct_1001&name=Jane%20Doe" \
  $NLIC/licensee

# created or renewed: one license per period, <subscription uuid>-<period>
curl --user $AUTH --header "Accept: application/json" \
  --data "number=63ab531e1d5b1d47eaf1ef44eeb853c3-2026-11&licenseeNumber=acct_1001&licenseTemplateNumber=EUJK1BXQ52" \
  $NLIC/license

# expired, or refunded
curl --user $AUTH --header "Accept: application/json" \
  --data "active=false" \
  $NLIC/license/63ab531e1d5b1d47eaf1ef44eeb853c3-2026-11

PQVJQ5F7H and EUJK1BXQ52 stand for your product and license template numbers. A create with a number that already exists returns HTTP 400 with DuplicateEntityException. That makes both calls safe to repeat: send the licensee create with every paid event and continue when it reports a duplicate, and treat a duplicate license as “already done” and answer the platform with 2xx (see IDs as numbers).

Sources


You run this integration yourself; NetLicensing does not host a Recurly connector. The Recurly details on this page are based on the sources above, and the NetLicensing calls it relies on were tested against the live API. Vendors change their APIs, so verify the details against the linked pages before you go live. Back to Billing and Commerce Platforms.