FastSpring Licensing Integration
In short: FastSpring sends signed webhooks for every order, rebill, refund and chargeback. A small receiver that you run turns them into NetLicensing licenses: one license per paid order item and per paid billing period, deactivated on refund or chargeback. No NetLicensing Gateway is involved, and the setup and a tested receiver script are part of this guide.
| Page | What it gives you |
|---|---|
| This page | How the integration works, which events it uses, and why |
| Step-by-step setup | NetLicensing and FastSpring configuration, deployment, testing, going live |
| Receiver script | Copy-paste Python receiver and backfill script (standard library only) |
| Event examples | Each FastSpring event with its payload and the NetLicensing calls it causes |
Buyer FastSpring Your receiver NetLicensing
| | | |
|-- order -->| | |
| |-- order.completed (signed) ------>| verify X-FS-Signature |
| | |-- POST /licensee ------------->| number = FastSpring account id
| | |-- POST /license -------------->| number = <order id>-<product>
| |<-- 200 ---------------------------| |
| | | |
| |-- subscription.charge.completed ->|-- POST /license -------------->| one per new billing period
| |-- return.created / chargeback --->|-- POST /license/{n} active=false
| | | |
| app ------------------------------------------------- POST /licensee/{email}/validate -->|
Gateway connector or webhooks?
| This guide (webhooks) | Gateway connector | |
|---|---|---|
| Runs where | A receiver you host | The NetLicensing Gateway |
| First purchase | order.completed webhook | FastSpring Remote Server Request fulfillment |
| Renewals, refunds, chargebacks | Yes | No; it needs webhooks in addition |
| License key in the FastSpring receipt | No; the customer signs in with the purchase email | Yes; the licensee number |
| Request authenticity | HMAC-SHA256 (X-FS-Signature), documented by FastSpring | Hash check based on the store’s private key, as implemented in the Gateway |
| Recovery of missed events | FastSpring retries for 7 days, and /events/unprocessed | Gateway log for 30 days |
Use this guide for new integrations. Keep the Gateway connector only if your product needs a key printed in the FastSpring receipt.
How licenses are numbered
The receiver uses FastSpring’s own ids as NetLicensing numbers, so every later event can address the right entity and repeated deliveries cannot create duplicates (see IDs as numbers).
| NetLicensing entity | Number | Taken from |
|---|---|---|
| Licensee | FastSpring account id, for example abCdE1FGH2Hij3KLMnOpqR | account.id |
| Licensee alias | The buyer’s email address | account.contact.email (or customer.email) |
| License | <order id>-<product path>, for example aBCDE12fGH3iJkL4mNOpq-cloud-storage | Order id and items[].product |
The email alias lets your application validate with the address the customer knows: POST /licensee/jane.doe@company.com/validate works the same as validating by the account id. We tested both on the live API. An alias is unique: a second licensee with the same alias is rejected with DuplicateEntityException.
Events used and what they do
| FastSpring event | What the receiver does |
|---|---|
order.completed | Creates the licensee (account id, email alias) and one license per item that is in your product map. For a subscription item whose state is trial, no license is created yet. For a paid subscription item it also records the subscription’s billing period (sequence) on the licensee |
subscription.charge.completed | A rebill. If its sequence is higher than the last licensed period of that subscription, creates one license for the period and records the new sequence; otherwise does nothing |
return.created | Deactivates the licenses of the returned items: <original.id>-<items[].product> |
chargeback.created | The event names only the order (order), so the receiver deactivates <order>-<product> for every product in your map; licenses that do not exist are skipped |
Why the sequence check: FastSpring states that subscription.charge.completed also fires for prorated charges, for example after a plan change, and documents sequence as the number of the current billing period. A prorated charge inside a period that is already licensed therefore adds no time. The last licensed period is stored on the licensee as the custom property fsSeq_<subscription id>.
Events you can leave out with time-based plans:
| Event | Why it is not needed |
|---|---|
subscription.activated | Fires for new subscriptions and trials; order.completed already covers the first purchase |
subscription.canceled, subscription.deactivated | Without a new charge, no new license arrives and access ends when the paid time runs out |
subscription.paused, subscription.resumed | A paused subscription is not charged; the next charge after resuming adds a period |
subscription.charge.failed, subscription.payment.overdue | Nothing to do; the customer keeps the time already paid for. Add a grace period if you want more |
This model fits the NetLicensing Subscription licensing model, where each license adds its time volume after the time already bought. Map each FastSpring subscription product to a template with the same period, for example a monthly product to a template of 1 month.
Check that the request comes from FastSpring
Set an HMAC SHA256 Secret on the webhook URL endpoint. FastSpring computes an HMAC-SHA256 of the raw request body with that secret, Base64-encodes it and sends it in the X-FS-Signature header; the header name can arrive in any letter case. The receiver recomputes it over the raw body and rejects a mismatch with 401. FastSpring also states that webhooks come from the IP address 107.23.30.83, and that IP addresses can be spoofed.
Retries and duplicates
| Your answer | FastSpring treats it as |
|---|---|
200 | All events in the post are processed |
202 with event ids in the body, one per line | Only the listed events are processed; the others are retried |
| Any other status | Failed; FastSpring recommends a 5xx code |
FastSpring retries unprocessed events for up to 7 days (after 1, 2, 4, 6, 6, 6 hours, then once a day), and automatic retries carry the same event id. One post can contain several events. The receiver answers 200 when every event succeeded and 202 with the ids of the successful ones otherwise. Because license numbers come from FastSpring ids, a repeated event creates nothing new.
For events that still fail, the backfill script reads GET /events/unprocessed from the FastSpring API, processes the events and marks them processed. FastSpring notes that the /events endpoint only covers events posted to the first webhook on the Webhooks page.
Requirements and limits of the foundation script
| Topic | Behavior |
|---|---|
| Webhook Expansion | Must be enabled: the receiver needs the expanded account, order and subscription objects, and fails events without them so that FastSpring retries |
| NetLicensing products | One NetLicensing product per FastSpring store; several FastSpring products map to templates of that product |
| Quantity | One license per order item, whatever the quantity |
| Partial refunds | Treated like full refunds: the item’s license is deactivated |
| Test orders | Acknowledged and ignored unless ACCEPT_TEST_EVENTS=true |
| Email changes | Not synchronized; the alias keeps the purchase email |
The script is a foundation: extend it where your licensing differs, for example quantity-based licenses or a licensing model without an end date.
Sources
- FastSpring: webhooks overview
- FastSpring: message security
- FastSpring: processed and unprocessed webhook events
- FastSpring: webhook expansion
- FastSpring: order.completed
- FastSpring: subscription.charge.completed
- FastSpring: subscription.activated
- FastSpring: subscription.deactivated
- FastSpring: return.created
- FastSpring: chargeback.created
- FastSpring: list all unprocessed events
You run this integration yourself; NetLicensing does not host it. The FastSpring details on this page are based on the sources above, and the receiver was run end to end against the live NetLicensing API with FastSpring-shaped test events. Vendors change their APIs, so verify the details against the linked pages before you go live. Back to Billing and Commerce Platforms.