---
updatedAt: 2026-04-25T00:10:18.000Z
agentTools:
  projectIndex: https://documentation.lootably.com/llms.txt
---

# Conversion Webhooks

Conversion Webhooks allow you to receive a POST request when a conversion is pending, rejected, or approved.

<br />

# 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](https://documentation.lootably.com/docs/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 <business@lootably.com> to receive the latest list.

We provide a [TypeScript type definition below](https://documentation.lootably.com/docs/conversion-webhooks#typescript-types) 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`.

<Table align={["left","left","left"]}>
  <thead>
    <tr>
      <th>
        Key
      </th>

      <th>
        Type
      </th>

      <th>
        Description
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        event
      </td>

      <td>
        "approved" | "pending" | "rejected"
      </td>

      <td>
        The type of conversion event. Matches the `x-lootably-webhook-type` header.
      </td>
    </tr>

    <tr>
      <td>
        data.placementID
      </td>

      <td>
        String
      </td>

      <td>
        The ID of the placement this conversion belongs to.
      </td>
    </tr>

    <tr>
      <td>
        data.userID
      </td>

      <td>
        String
      </td>

      <td>
        The ID of the user that completed the offer, as originally passed to us from your system.
      </td>
    </tr>

    <tr>
      <td>
        data.transactionID
      </td>

      <td>
        String
      </td>

      <td>
        The ID of this conversion in our database.
      </td>
    </tr>

    <tr>
      <td>
        data.conversionTime
      </td>

      <td>
        String
      </td>

      <td>
        The ISO 8601 UTC timestamp of when the conversion was recorded.
      </td>
    </tr>

    <tr>
      <td>
        data.ip
      </td>

      <td>
        String
      </td>

      <td>
        The IP address of the user.
      </td>
    </tr>

    <tr>
      <td>
        data.countryCode
      </td>

      <td>
        String
      </td>

      <td>
        The [ISO 3166 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country code of the user.
      </td>
    </tr>

    <tr>
      <td>
        data.offerName
      </td>

      <td>
        String
      </td>

      <td>
        The name of the offer the user completed.
      </td>
    </tr>

    <tr>
      <td>
        data.offerID
      </td>

      <td>
        String
      </td>

      <td>
        The ID of the offer the user completed.
      </td>
    </tr>

    <tr>
      <td>
        data.revenue
      </td>

      <td>
        Number
      </td>

      <td>
        The USD revenue you generated from the conversion.
      </td>
    </tr>

    <tr>
      <td>
        data.currencyReward
      </td>

      <td>
        Number
      </td>

      <td>
        The user's reward, calculated based on the settings from your placement's Currency tab.
      </td>
    </tr>

    <tr>
      <td>
        data.extraSubIDs
      </td>

      <td>
        Object
      </td>

      <td>
        An object containing  `sid2`, `sid3`, `sid4`, or `sid5` - the custom parameters you optionally pass through the offerwall or tracking URL. Each `sid` value will be  `string | null`.
      </td>
    </tr>

    <tr>
      <td>
        data.offerType
      </td>

      <td>
        "singlestep" | "multistep"
      </td>

      <td>
        The type of offer completed.

        **Multistep conversions include additional goal properties described below.**
      </td>
    </tr>
  </tbody>
</Table>

## 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.

```typescript TypeScript
// 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.

```javascript TypeScript
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.**