Webhooks
In case you want to perform your own actions when events happen in Moonbase, you can configure webhooks to forward events to your own APIs. Webhooks can be set up to trigger on a number of different events:
- Payment events
OrderPaidOrderCompletedOrderPayoutScheduledOrderRefunded
- Licensing events
LicenseActivatedLicenseProvisionedTrialActivated
- Product release events
ProductReleasePublished
- Subscription events
SubscriptionStartedSubscriptionRenewedSubscriptionExpiredSubscriptionCancelledSubscriptionCompleted
- Voucher events
VoucherCodeRedeemed
- Customer events
CustomerSignedUpCustomerSubscribedCustomerUnsubscribed
To get started with webhooks, head over to your Moonbase account settings, and set up your first webhook:

Once created, it's immediately active and you will have access to the secret key used to compute the signature as described in Security.
Do you have a specific scenario you need webhooks for that is not covered by the current events and content?
Reach out to us through the support channel, or at developers@moonbase.sh.
Security
To ensure that the webhook requests are in fact originating from Moonbase and not some malicious actor, we apply a HMAC-SHA256 algorithm on the body of the request and include this signature in a X-Signature header on the request.
Example signature verification code
using var algorithm = new HMACSHA256(Encoding.UTF8.GetBytes(secretKey));
var json = await request.Content.ReadAsStringAsync();
var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(json));
var signature = Convert.ToBase64String(hash).ToUpperInvariant();
var sentSignature = request.Headers.GetValues("X-Signature").Single();
Assert.Equal(sentSignature, signature);
Should you ever need to change the secret key, you can do so by editing the webhook.
JSON structure
All webhook requests being sent by Moonbase will have a fixed structure with some basic details about the event that occurred. Additionally, relevant resources are also attached, so that you don't have to query the Moonbase API to fetch more details. The shape of these resources differ between events, and are described on this page.
- Name
id- Type
- string
- Description
Identifier of this unique webhook request.
- Name
endpoint- Type
- string
- Description
The endpoint the request is being sent to.
- Name
eventType- Type
- enum
- Description
The event being communicated, will one of the events listed in this article.
- Name
timestamp- Type
- datetime
- Description
ISO-8601 date and time of the time at which the event occurred.
- Name
resource- Type
- object
- Description
The resource that the event relates to:
- Name
type- Type
- enum
- Description
One of:
Order,License,Trial,ProductRelease,Subscription,VoucherCode,Customer
- Name
data- Type
- object
- Description
The actual contents of the entity. See below for more details about how this is structured.
- Name
customer- Type
- object
- Nullability
- nullable
- Description
Details about the customer associated with the event. In case the customer is unknown, it will be null.
- Name
id- Type
- string
- Description
Unique identifier of the customer.
- Name
name- Type
- string
- Description
Name of the customer.
- Name
email- Type
- string
- Description
Email address of the customer.
- Name
isDeleted- Type
- boolean
- Description
Flag showing if the customer account was deleted
- Name
properties- Type
- object
- Optionality
- optional
- Description
Any custom properties configured on the customer, keyed by name. Omitted when the customer has none. See Customer events for the shape of each value.
Webhook request
{
"id": "a885619a-cb5e-41a6-a07b-1be4e520cccb",
"endpoint": "https://api.my-domain.example/webhook",
"eventType": "OrderCompleted",
"timestamp": "2024-11-11T11:11:11.0000000Z",
"resource": {
"type": "Order",
"data": {...}
},
"customer": {
"id": "92c0f540-9044-4b00-af4c-fe29f5da355e",
"name": "Example customer name",
"email": "user@example.com",
"isDeleted": false
}
}
Payment Events
When customers make purchases through Moonbase, they make them on what we call an "Order". This is why the events related to payments are all prefixed with Order, and the typical lifecycle of an order goes like this:
- The customer pays for an order, a
OrderPaidevent is sent (not applicable for free purchases) - Moonbase fulfills the order, issuing licenses and sending receipts, completing the order, a
OrderCompletedevent is sent - Moonbase then calculates all affiliate revenue splits and schedules payout for the order, a
OrderPayoutScheduledevent is sent
Lastly, if you refund the order, Moonbase will emit a OrderRefunded event to any webhook listening. The same event is sent for both full and partial refunds: check isFullyRefunded and isPartiallyRefunded to tell them apart, and read the refundHistory array for the amounts and line items covered by each refund.
All events concering orders will have the same shape of the order:
- Name
id- Type
- string
- Description
Unique identifier of the order.
- Name
status- Type
- enum(Paid|Completed)
- Description
The current status of the order,
Paidif not yet completed,Completedif paid and fulfilled.
- Name
currency- Type
- string
- Description
The currency used for this purchase.
- Name
completedAt- Type
- DateTime
- Optionality
- optional
- Description
The timestamp for when the order was completed.
- Name
refundedAt- Type
- DateTime
- Optionality
- optional
- Description
The timestamp for when the order was fully refunded. Omitted while the order is not fully refunded.
- Name
isFullyRefunded- Type
- boolean
- Description
Whether the entire order has been refunded. Remains
falsefor a partial refund, even thoughrefundHistorylists the refunded amounts.
- Name
isPartiallyRefunded- Type
- boolean
- Description
Whether the order has been partially but not fully refunded.
trueonce at least one refund has settled while the order still has unrefunded units.
- Name
refundHistory- Type
- array
- Optionality
- optional
- Description
The settled refunds applied to this order, present once at least one refund has settled. Each entry describes a single refund:
- Name
id- Type
- string
- Description
Unique identifier of the refund.
- Name
at- Type
- DateTime
- Description
The timestamp for when this refund was made.
- Name
amount- Type
- object
- Description
The amount refunded to the customer in this refund, containing:
currency: The currency of the refunded amount.amount: The refunded amount.
- Name
lineItems- Type
- array
- Description
The order line items covered by this refund, each containing:
- Name
lineItemId- Type
- string
- Description
Identifier of the refunded order line item. Matches the
lineItemIdof an entry in the order'sitemsarray.
- Name
quantity- Type
- number
- Description
The number of units refunded for this line item in this refund.
- Name
amount- Type
- object
- Description
The amount refunded to the customer for this line item, with the same
currencyandamountproperties as above.
- Name
isDisputed- Type
- boolean
- Description
Flag indicating if this purchase has been disputed.
- Name
total- Type
- object
- Description
Calculated total for all items part of the order, contains:
original: The original amount before discountsdiscount: The total amount of discounts addedsubtotal: The total amount after discounts have been removedtaxes: Total taxes to pay, may be inclusive or exclusive of thesubtotaldepending on configuration, currency and region.due: How much is being paid in total by the customer
- Name
payout- Type
- object
- Optionality
- optional
- Description
Payout details for this order, only present if an amount is to be paid. Note that this may get updated as affiliate revenue splits are calculated later in the pipeline.
subtotal: The original amount paid by the customertaxes: The total amount of taxes collected and remittedplatformFees: The total amount of fees going to Moonbasedue: How much is being paid out to you
- Name
billingDetails- Type
- object
- Description
Billing details about the customer who made the purchase:
- Name
name- Type
- string
- Description
Full name of the customer.
- Name
businessName- Type
- string
- Nullability
- nullable
- Description
In case the purchase was a business purchase, this field will have the name.
- Name
taxId- Type
- string
- Nullability
- nullable
- Description
In case the purchase was a business purchase, this field will have the tax id if given.
- Name
email- Type
- string
- Description
Email address of the customer.
- Name
phone- Type
- string
- Nullability
- nullable
- Description
Phone number of the customer. Note that collecting phone numbers is an optional feature in Moonbase and not activated by default.
- Name
address- Type
- object
- Nullability
- nullable
- Description
Billing address for the customer, not present if the purchase was a lead generator purchase. Contains the following properties:
- Name
countryCode- Type
- string
- Description
ISO 3166-1 alpha-2 two-letter country code.
- Name
streetAddress1- Type
- string
- Description
First line of the regular street address.
- Name
streetAddress2- Type
- string
- Optionality
- optional
- Description
Second line of the regular street address.
- Name
postCode- Type
- string
- Description
Postal code of the address.
- Name
locality- Type
- string
- Description
Locality of the address, only required if no region is given.
Also known asCity.
- Name
region- Type
- string
- Description
Region of the address, only required if no locality is given.
Also known asState.
- Name
items- Type
- array
- Description
Collection of items part of the order. Note that this can be either products or bundles, and they are discriminated using the
typeproperty. Some properties are present on both types, others are shared:- Name
type- Type
- 'Product'
- Description
Item type discriminator.
- Name
productId- Type
- string
- Description
The unique ID of the product.
Product line item
- Name
type- Type
- 'Bundle'
- Description
Item type discriminator.
- Name
bundleId- Type
- string
- Description
The unique ID of the bundle.
Bundle line item
- Name
lineItemId- Type
- string
- Description
Stable identifier of this order line item. Use it to correlate entries in
refundHistoryback to the line they refunded.
- Name
quantity- Type
- number
- Description
The quantity of this item.
- Name
price- Type
- object
- Description
The original price of the product or bundle.
- Name
total- Type
- object
- Description
The calculated total for this line item.
- Name
variation- Type
- object
- Description
Details about the selected pricing variation for this item.
- Name
fulfillment- Type
- object
- Optionality
- optional
- Description
Details about the license fulfillment for this purchase.
Shared
Order data example
{
"id": "dc0b53f9-4e43-4179-8554-00f2a8228a25",
"status": "Completed",
"currency": "EUR",
"completedAt": "2024-11-11T11:11:11.0000000Z",
"isFullyRefunded": false,
"isPartiallyRefunded": false,
"isDisputed": false,
"total": {
"original": {
"currency": "EUR",
"amount": 10
},
"discount": {
"currency": "EUR",
"amount": 5
},
"subtotal": {
"currency": "EUR",
"amount": 5
},
"taxes": {
"currency": "EUR",
"amount": 5
},
"due": {
"currency": "EUR",
"amount": 5
}
},
"billingDetails": {
"name": "Example User",
"businessName": null,
"taxId": null,
"email": "user@example.com",
"phone": null,
"address": {
"countryCode": "NO",
"streetAddress1": "Utsikten 6",
"streetAddress2": null,
"locality": "Skien",
"region": null,
"postCode": "3718"
}
},
"couponsApplied": [
{
"id": "43396b94-53b4-4901-8e9e-ab1d6ee7d3b5",
"code": "MY-UNIQUE-CODE",
"name": "Half off coupons",
"description": "Thanks for being our customer!",
"discount": {
"type": "PercentageOffDiscount",
"percentage": 0.5
},
"applicableProductVariations": {},
"applicableBundleVariations": {}
}
],
"items": [
{
"type": "Product",
"lineItemId": "7c1f0b2a-9d3e-4a5b-8c6d-0e1f2a3b4c5d",
"productId": "example-product",
"quantity": 1,
"price": {
"EUR": 10
},
"total": {
"original": {
"currency": "EUR",
"amount": 10
},
"discount": {
"currency": "EUR",
"amount": 5
},
"subtotal": {
"currency": "EUR",
"amount": 5
},
"due": {
"currency": "EUR",
"amount": 5
}
},
"variation": {
"id": "v802c5",
"name": "Default",
"entitlement": {
"type": "PerpetualLicense"
},
"recurrence": {
"type": "OneOff"
},
"price": {
"EUR": 10
}
},
"fulfillment": {
"type": "License",
"licenseIds": [
"d564a47a-d7f3-48ea-8698-57ad7512b11d"
]
}
}
]
}
When an order is only partially refunded, isPartiallyRefunded is true while isFullyRefunded stays false, no order-level refundedAt is set, and the refundHistory array carries the details of each settled refund:
Order data example (partially refunded)
{
"id": "dc0b53f9-4e43-4179-8554-00f2a8228a25",
"status": "Completed",
"currency": "EUR",
"completedAt": "2024-11-11T11:11:11.0000000Z",
"isFullyRefunded": false,
"isPartiallyRefunded": true,
"isDisputed": false,
"total": {
"original": { "currency": "EUR", "amount": 20 },
"discount": { "currency": "EUR", "amount": 0 },
"subtotal": { "currency": "EUR", "amount": 20 },
"taxes": { "currency": "EUR", "amount": 5 },
"due": { "currency": "EUR", "amount": 20 }
},
"refundHistory": [
{
"id": "b3f1c2a4-6d8e-4f51-9c7a-1d2e3f4a5b6c",
"at": "2024-11-12T09:30:00.0000000Z",
"amount": { "currency": "EUR", "amount": 10 },
"lineItems": [
{
"lineItemId": "8f2c1b4e-3a6d-4f51-9c7a-1d2e3f4a5b6c",
"quantity": 1,
"amount": { "currency": "EUR", "amount": 10 }
}
]
}
],
"items": [
{
"type": "Product",
"lineItemId": "8f2c1b4e-3a6d-4f51-9c7a-1d2e3f4a5b6c",
"productId": "example-product",
"quantity": 2,
"price": { "EUR": 10 },
"total": {
"original": { "currency": "EUR", "amount": 20 },
"discount": { "currency": "EUR", "amount": 0 },
"subtotal": { "currency": "EUR", "amount": 20 },
"due": { "currency": "EUR", "amount": 20 }
}
}
]
}
Licensing events
We currently expose the following licensing related events:
LicenseActivated: Happens the first time a license is activated on any deviceLicenseProvisioned: Happens when a license is provisioned for a customer, for example after an order completes, a voucher is redeemed, or a subscription is migrated. Not sent for imported licenses.TrialActivated: Happens the first time a trial is started on any device
These events have different resource shapes:
License events
Both LicenseActivated and LicenseProvisioned carry the same license resource shape:
- Name
id- Type
- string
- Description
Unique identifier of the license.
- Name
externalId- Type
- string
- Optionality
- optional
- Description
Your own external identifier for the license, if one was set. Omitted when not set.
- Name
status- Type
- enum(Active|Revoked|Expired|Pending)
- Description
The current status of the license.
- Name
productId- Type
- string
- Description
The product ID that the license is for.
- Name
maxNumberOfActivations- Type
- number
- Description
The maximum number of devices the license can be activated on.
- Name
offlineActivationsAllowed- Type
- boolean
- Description
Whether the license is allowed to be activated offline.
- Name
properties- Type
- object
- Optionality
- optional
- Description
Custom properties configured on the license, keyed by name. Omitted when the license has no custom properties. Each value contains:
- Name
type- Type
- enum(text|number|date|boolean|object|array)
- Description
The type of the property value.
- Name
value- Description
The property value, shaped according to
type.
- Name
public- Type
- boolean
- Description
Whether the property is exposed publicly.
- Name
includeInToken- Type
- boolean
- Description
Whether the property is included in issued license tokens.
License data example
{
"id": "5ec722c7-f7d9-441d-83af-69ea634e429b",
"externalId": "EXT-12345",
"status": "Active",
"productId": "example-product",
"maxNumberOfActivations": 5,
"offlineActivationsAllowed": false,
"properties": {
"deployment": {
"type": "text",
"value": "production",
"public": false,
"includeInToken": true
}
}
}
Trial events
- Name
id- Type
- string
- Description
Unique identifier of trial.
- Name
status- Type
- enum(Active|Expired)
- Description
The current status of the trial.
- Name
productId- Type
- string
- Description
The product ID being trialled.
- Name
lastValidatedAt- Type
- DateTime
- Description
The time of last validation.
- Name
expiresAt- Type
- DateTime
- Description
The time at which this trial expires.
Trial data example
{
"id": "7cda66c7d03bd35bac892b97436c33b8",
"status": "Active",
"productId": "example-product",
"lastValidatedAt": "2024-11-11T11:11:11.0000000Z",
"expiresAt": "2025-11-11T11:11:11.0000000Z"
}
Product release events
We currently expose the following product release related events:
ProductReleasePublished: Happens every time a new version of a product is being released
Event
- Name
productId- Type
- string
- Description
The product ID of the released product
- Name
version- Type
- string
- Description
The version of the release
- Name
description- Type
- string
- Nullability
- nullable
- Description
The description of the release
- Name
publishedAt- Type
- DateTime
- Description
The time at which the product release was published
Product release data example
{
"productId": "example-product",
"version": "1.2.3",
"description": "Enhanced performance and critical bug fixes",
"publishedAt": "2024-11-11T11:11:11.0000000Z"
}
Subscription events
We currently expose the following subscription related events:
SubscriptionStarted: Happens every time a new subscription is startedSubscriptionRenewed: Happens every time a subscription is renewedSubscriptionExpired: Happens every time a subscription expiresSubscriptionCancelled: Happens every time a subscription is cancelledSubscriptionCompleted: Happens when a subscription reaches its natural completion, for example when a fixed-term subscription runs its course
Event
- Name
id- Type
- string
- Description
The unique ID of the subscription
- Name
status- Type
- enum(Active|Cancelled|Expired|Completed)
- Description
The current status of the subscription.
Completedis used once the subscription has reached its natural completion.
- Name
expiresAt- Type
- DateTime
- Description
The time at which the subscription expires unless sucessfully renewed.
- Name
renewedAt- Type
- DateTime
- Nullability
- nullable
- Description
The last time at which the subscription was renewed, null if never renewed.
- Name
startedAt- Type
- DateTime
- Description
The time at which the subscription started originally.
- Name
total- Type
- object
- Description
Calculated total to be paid for each subscription cycle, contains:
original: The original amount before discountsdiscount: The total amount of discounts addedsubtotal: The total amount after discounts have been removedtaxes: Total taxes to pay, may be inclusive or exclusive of thesubtotaldepending on configuration, currency and region.due: How much is being paid in total by the customer
- Name
cycleLength- Type
- enum(Monthly|Yearly)
- Description
The length of each subscription cycle on this subscription.
- Name
content- Type
- object
- Description
The content of the subscription, can either be a product or a bundle:
Product content
- Name
type- Type
- 'Product'
- Description
Content type discriminator.
- Name
productId- Type
- string
- Description
ID of the product.
- Name
quantity- Type
- number
- Description
The number of licenses being subscribed to.
- Name
fulfillment- Type
- object
- Description
The fulfillment details for this product, will contain the following properties:
- Name
licenseIds- Type
- string[]
- Description
A list of license IDs that has been fulfilled for this fulfillment.
Bundle content
- Name
type- Type
- 'Bundle'
- Description
Content type discriminator.
- Name
bundleId- Type
- string
- Description
ID of the bundle.
- Name
quantity- Type
- number
- Description
The number of licenses being subscribed to for each product in the bundle.
- Name
fulfillment- Type
- object
- Description
The fulfillment details for this bundle, will contain the following properties:
- Name
licenseIds- Type
- string[]
- Description
A list of license IDs that has been fulfilled for this fulfillment.
Subscription data example
{
"id": "4e794c6a-56c3-4a2b-a568-a99eaa0d3b7f",
"status": "Active",
"expiresAt": "2025-02-14T21:26:45.3769978Z",
"renewedAt": "2025-02-13T21:26:45.3769978Z",
"startedAt": "2025-02-08T21:08:43.6530272Z",
"total": {
"original": {
"currency": "EUR",
"amount": 15
},
"discount": {
"currency": "EUR",
"amount": 0
},
"subtotal": {
"currency": "EUR",
"amount": 12.4
},
"taxes": {
"currency": "EUR",
"amount": 2.6
},
"due": {
"currency": "EUR",
"amount": 15
}
},
"cycleLength": "Monthly",
"content": {
"type": "Product",
"quantity": 1,
"fulfillment": {
"type": "License",
"licenseIds": [
"922c8100-e57e-48e6-9ea8-a0e00db52c13"
]
},
"productId": "example-product"
}
}
Voucher events
We currently expose the following voucher related events:
VoucherCodeRedeemed: Happens every time a code of a voucher is redeemed by a customer
Event
- Name
code- Type
- string
- Description
The code being redeemed
- Name
redeemedBy- Type
- string
- Description
Unique ID of the customer that redeemed this code
- Name
redeemedAt- Type
- datetime
- Description
ISO-8601 date and time for when the redemption took place.
- Name
voucher- Type
- object
- Description
Details about the parent voucher object, including:
- Name
id- Type
- string
- Description
Unique ID of this voucher
- Name
name- Type
- string
- Description
Name as configured in your Moonbase account
- Name
description- Type
- string
- Description
Description as configured in your Moonbase account
- Name
redeemsProducts- Type
- array
- Description
List of products that this voucher redeems, each object in the list contains:
- Name
value- Type
- string
- Description
The ID of the product
- Name
quantity- Type
- number
- Description
Number of licenses provisioned per redemption
- Name
redeemsBundles- Type
- array
- Description
List of bundles that this voucher redeems, each object in the list contains:
- Name
value- Type
- string
- Description
The ID of the bundle
- Name
quantity- Type
- number
- Description
Number of licenses provisioned per redemption
Voucher data example
{
"code": "EXAMPLE-VOUCHER-CODE",
"redeemedBy": "922c8100-e57e-48e6-9ea8-a0e00db52c13",
"redeemedAt": "2025-02-14T21:26:45.3769978Z",
"voucher": {
"id": "4e794c6a-56c3-4a2b-a568-a99eaa0d3b7f",
"name": "Example voucher",
"description": "This voucher is used for demo purposes",
"redeemsProducts": [
{ "value": "example-product-id", "quantity": 1 },
],
"redeemsBundles": [
{ "value": "example-bundle-id", "quantity": 1 },
],
},
}
Customer events
We currently expose the following customer related events:
CustomerSignedUp: Happens when a customer creates a Moonbase account, either by signing up themselves or when an account is created for them during checkout or license provisioning. Not sent for imported customers or customers registered without an account.CustomerSubscribed: Happens when a customer opts in to your newsletter and marketing communications.CustomerUnsubscribed: Happens when a customer opts out of your newsletter and marketing communications.
All three events share the same customer resource shape:
- Name
id- Type
- string
- Description
Unique identifier of the customer.
- Name
name- Type
- string
- Description
Name of the customer.
- Name
email- Type
- string
- Description
Email address of the customer.
- Name
isDeleted- Type
- boolean
- Description
Flag showing if the customer account was deleted.
- Name
properties- Type
- object
- Optionality
- optional
- Description
Custom properties configured on the customer, keyed by name. Omitted when the customer has no custom properties. Each value contains:
- Name
type- Type
- enum(text|number|date|boolean|object|array)
- Description
The type of the property value.
- Name
value- Description
The property value, shaped according to
type.
- Name
public- Type
- boolean
- Description
Whether the property is exposed publicly.
- Name
includeInToken- Type
- boolean
- Description
Whether the property is included in issued tokens.
Customer data example
{
"id": "92c0f540-9044-4b00-af4c-fe29f5da355e",
"name": "Example customer name",
"email": "user@example.com",
"isDeleted": false,
"properties": {
"industry": {
"type": "text",
"value": "Technology",
"public": true,
"includeInToken": false
}
}
}