Conversion Webhooks
Conversion Webhooks allow you to receive a POST request when a conversion is pending, rejected, or approved.
What is a Conversion Webhook?
A Conversion Webhook is an HTTPS POST request sent to an endpoint you control whenever one of your user's conversions changes state. Unlike Postbacks, which send a GET request with query parameters, Conversion Webhooks send a POST request with a JSON body and a secret header. Configuring Conversion Webhooks allows you to reconcile transactions, update user balances, and run fraud or reporting pipelines in real time.
We support three Conversion Webhook types, each configurable independently per placement. All three share the same request body format - only the event field and the x-lootably-webhook-type header change.
| Type | When it fires |
|---|---|
| approved | The conversion has been approved. Fired whenever a conversion passes all Lootably fraud protection rules. |
| pending | The conversion has entered the pending state and is awaiting further review. |
| rejected | The conversion has been rejected. Fired whenever a conversion is flagged as fraudulent and will not be paid out. |
Configuring your Conversion Webhooks
Begin by navigating to the Webhooks tab on your placement's settings page. Here, you can set an endpoint URL for each of the webhook types.
NOTE: You do not need to configure all three endpoints, only the endpoint(s) you would like to subscribe to.
Each placement also has a unique Webhook Secret which is included on every request so you can verify authenticity. You can rotate this value at any time from the same Webhooks tab.
Request Format
All Conversion Webhooks are sent as an HTTPS POST request with the following characteristics:
- Method:
POST - Content-Type:
application/json - User-Agent:
Lootably-Webhook-Bot/1.0 (+https://documentation.lootably.com/docs/webhooks)
Conversion Webhooks are sent from a fixed set of Lootably egress IPs. If your system firewalls inbound traffic, please reach out to [email protected] to receive the latest list.
We provide a TypeScript type definition below for the request body JSON data.
Headers
Every Conversion Webhook includes the following Lootably-specific headers:
| Header | Type | Description |
|---|---|---|
| x-lootably-webhook-id | String | A unique ID for this webhook request. Useful for de-duplication and idempotency in your system. Also useful for debugging. |
| x-lootably-webhook-timestamp | String | The ISO 8601 UTC timestamp of when the webhook was dispatched from our system. |
| x-lootably-webhook-secret | String | Your placement's Webhook Secret. Compare this value against the secret stored in your system to verify the request originated from Lootably. |
| x-lootably-webhook-type | String | The webhook type, one of approved, pending, or rejected. This value always matches the event field inside the request body. |
Body
The request body is always a JSON object with two top-level keys, event and data.
Key | Type | Description |
|---|---|---|
event | "approved" | "pending" | "rejected" | The type of conversion event. Matches the |
data.placementID | String | The ID of the placement this conversion belongs to. |
data.userID | String | The ID of the user that completed the offer, as originally passed to us from your system. |
data.transactionID | String | The ID of this conversion in our database. |
data.conversionTime | String | The ISO 8601 UTC timestamp of when the conversion was recorded. |
data.ip | String | The IP address of the user. |
data.countryCode | String | The ISO 3166 alpha-2 country code of the user. |
data.offerName | String | The name of the offer the user completed. |
data.offerID | String | The ID of the offer the user completed. |
data.revenue | Number | The USD revenue you generated from the conversion. |
data.currencyReward | Number | The user's reward, calculated based on the settings from your placement's Currency tab. |
data.extraSubIDs | Object | An object containing |
data.offerType | "singlestep" | "multistep" | The type of offer completed. Multistep conversions include additional goal properties described below. |
Multistep Conversions
When data.offerType is "multistep", the data object includes the following additional properties:
| Key | Type | Description |
|---|---|---|
| data.multistepOfferPercentageComplete | Number | The percentage from 0-100 (including decimals) of the offer completed by the user. |
| data.goalID | String | The ID of the goal completed by the user. |
| data.goalDescription | String | The description of the goal completed by the user. |
TypeScript Types
Below is the TypeScript definition for a Conversion Webhook's body.
// Types
import type InternalConversion from 'types/InternalConversion';
type ConversionWebhookBody<EventType extends 'approved' | 'pending' | 'rejected' = 'approved' | 'pending' | 'rejected'> = {
event: EventType,
data: {
placementID: string,
userID: string,
transactionID: string,
conversionTime: string,
ip: string,
countryCode: string,
offerName: string,
offerID: string,
revenue: number,
currencyReward: number,
extraSubIDs: {
sid2: string | null,
sid3: string | null,
sid4: string | null,
sid5: string | null,
},
} & (
| {
offerType: 'singlestep',
}
| {
offerType: 'multistep',
multistepOfferPercentageComplete: number,
goalID: string,
goalDescription: string,
}
),
};
export default ConversionWebhookBody;
Validating Webhooks
To validate that a Conversion Webhook originated from Lootably, compare the x-lootably-webhook-secret header against the Webhook Secret listed on your placement's Webhooks tab. If the values match, the request came from our system.
if(req.headers['x-lootably-webhook-secret'] !== process.env.LOOTABLY_WEBHOOK_SECRET) {
return res.status(401).send('Invalid webhook secret');
}If you suspect your Webhook Secret has been exposed, you can rotate it at any time from the Webhooks tab.
Response
Return a 2xx status code to acknowledge a Conversion Webhook. Any non-2xx response, or a request that times out, is treated as a failure.
Webhooks may be resent up to five times until a successful 2xx response is received.
Updated about 2 hours ago
