SendOwl Licensing Integration
In short: SendOwl signs its webhooks with HMAC-SHA256. Your receiver creates a license for each product in a completed one-time order, one license per subscription payment, and deactivates it on cancellation, refund or chargeback.
SendOwl sells digital products. A Gateway connector for SendOwl was requested (issue #5) but does not exist. The webhook route below does not need one.
Create the webhook in Settings → Advanced settings → Developer → Webhooks → Add a webhook. You can limit it to orders that contain a product, a discount code, or a sale referred by an affiliate.
What SendOwl sends
SendOwl sends an HTTP POST with a raw JSON body (not form parameters) that holds the order under an order key. The event that fired is in the header X-SENDOWL-EVENT; the body shows the order’s state at that moment. Useful fields: buyer_email, buyer_name, id (the order id), state, for_subscription, cart.cart_items[].product.id and order_custom_checkout_fields.
Map SendOwl events to NetLicensing
| SendOwl event | What to do in NetLicensing |
|---|---|
complete (Order completed) | One-time products: create a licensee from buyer_email, then a license for each cart item. Subscription orders are licensed by new_payment |
free (Free order issued) | Same as a completed order |
subscription_active | Create the licensee if it does not exist yet; no license |
new_payment | SendOwl: “A payment has been made on any type of order”, including the first payment of a subscription. For subscription orders, create one license per payment |
subscription_cancelling | Cancelled, access allowed until the period ends: deactivate at period end |
subscription_cancelled, subscription_complete | Deactivate the license (active=false) (subscription_complete: all payments were made; SendOwl does not revoke access) |
refund, chargeback | Deactivate the license (active=false) |
failed, fraud_review, in_dispute | No license, or hold the license until the order resolves |
Other events: imported (order imported from another system) and subscription_setup (the buyer set up a subscription at the gateway, and SendOwl waits for it to activate).
Check that the request comes from SendOwl
Webhooks you create in the dashboard now use the current scheme, which sends the header X-SENDOWL-HMAC-SHA256. Compute the SHA256 HMAC of the raw request JSON, keyed with the Secret of your Signing key (Settings → Advanced settings → Developer → SendOwl API), Base64-encode it and compare. Webhooks created under the original scheme still run, but send no signature and no event header, so upgrade them.
Retries and duplicates
SendOwl queues a request when an order changes state and sends it about 10 seconds later. A 2XX or 3XX response is a success. Any other response is retried with exponential back-off, up to 10 attempts in total; after the tenth failure SendOwl stops and emails the account admin. Test web hook sends a sample call to your URL.
Match the customer and the plan
The buyer is identified by buyer_email and the order by id; the sample payload has no separate subscription id. Map cart.cart_items[].product.id to your licenseTemplateNumber with a lookup table, or use the order’s custom checkout fields to carry your own value. SendOwl’s webhook page also points to its Zapier integration as a simpler alternative to webhooks.
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
SendOwl Your receiver NetLicensing
| | |
| new_payment ------------->| |
| | verify signature |
| | POST /licensee ------------>|
| | number=email |
| | POST /license ------------->|
| | number=order-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. SendOwl calls your receiver
POST /webhooks/sendowl HTTP/1.1
Content-Type: application/json
X-SENDOWL-EVENT: new_payment
X-SENDOWL-HMAC-SHA256: <base64 HMAC-SHA256>
{
"order": {
"id": 12345,
"state": "complete",
"for_subscription": true,
"buyer_email": "jane@example.com",
"buyer_name": "Jane Doe",
"cart": { "cart_items": [ { "product": { "id": 678 } } ] }
}
}
2. Your receiver checks the request
import base64, hashlib, hmac
def verify_sendowl(raw_body: bytes, signature_header: str, secret: str) -> bool:
# signature_header = request.headers["X-SENDOWL-HMAC-SHA256"]
digest = hmac.new(secret.encode(), raw_body, hashlib.sha256).digest()
return hmac.compare_digest(base64.b64encode(digest).decode(), signature_header)
Verify the raw request body exactly as received, before any JSON or form parsing. The key is the Secret of your SendOwl signing key. Webhooks created under the original scheme are not signed; recreate them.
3. Your receiver calls NetLicensing
NLIC=https://go.netlicensing.io/core/v2/rest
AUTH="apiKey:$NETLICENSING_API_KEY" # API key with ROLE_APIKEY_OPERATION
# complete or subscription_active: the buyer's email becomes the licensee number
curl --user $AUTH --header "Accept: application/json" \
--data "productNumber=PQVJQ5F7H&number=jane%40example.com&name=Jane%20Doe" \
$NLIC/licensee
# complete (one-time) or new_payment (subscription): one license per order item or payment
curl --user $AUTH --header "Accept: application/json" \
--data "number=so-12345-2026-11&licenseeNumber=jane%40example.com&licenseTemplateNumber=EUJK1BXQ52" \
$NLIC/license
# refund or chargeback
curl --user $AUTH --header "Accept: application/json" \
--data "active=false" \
$NLIC/license/so-12345-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).
The sample payload has no payment id, so build the number from the order id and the billing period you track.
Sources
You run this integration yourself; NetLicensing does not host a SendOwl connector. The SendOwl 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.