# Webhooks Reference

## How they work

Webhook URLs will receive a POST request with JSON data related to the event that triggered the webhook.

See [Events](#events) below for supported events and the associated JSON payloads.

## Error handling

There are things that can go wrong even before a request reaches its destination (e.g. DNS resolution error, sudden network failure, etc.) and even if it does, the target system may not always be able to correctly process the request. This is why the notification delivery system is built to handle these failures.

Our webhook delivery system uses response status to determine whether a request has been successful or not.

* 2XX response codes: The request is considered successful and the notification is considered as delivered, no retry needed.
* Any other response status or error (such as connection error, timeout, etc.) is handled as an unsuccessful delivery attempt and the task is rescheduled for later.

### Retries

If a webhook delivery fails, it gets retried. We use an exponential backoff strategy to determine how long to wait before attempting the delivery again.

We'll retry a webhook delivery up to 14 times (\~24 hours of retrying), after which the delivery will be marked as `retries exhausted` and retries will stop.

## Events

### `ad.uploaded`

This event is fired when an ad preview has been successfully captured. Use this to get notified when ads created via the Create Ad API are ready.

{% hint style="warning" %}
This event is only fired when a **new ad** is created. If you use a `lookup_key` and an existing ad is returned, no webhook is sent. Check the API response directly for the ad data.
{% endhint %}

{% hint style="info" %}
The `width` and `height` fields are optional and will only be present if the ad dimensions were successfully detected.
{% endhint %}

<details>

<summary>Payload</summary>

```json
{
  "id": "evt_{{uuid-of-event}}",
  "type": "event",
  "created_at": "2026-01-17T10:52:11.822-05:00",
  "topic": "ad.uploaded",
  "data": {
    "object": {
      "type": "ad",
      "id": "uuid-of-ad",
      "lookup_key": "your-lookup-key-or-null",
      "name": "Ad Name",
      "status": "uploaded",
      "category": "display",
      "kind": "html_tag",
      "width": 300,
      "height": 250,
      "preview_image_url": "https://app.adreform.com/path/to/preview.png",
      "html_url": "https://app.adreform.com/o/your-org/campaigns/uuid/ads/uuid",
      "campaign": {
        "id": "uuid-of-campaign",
        "name": "Campaign Name"
      }
    }
  }
}
```

</details>

***

### `screenshot_set.approved`

This event is fired when a set of screenshots is ready for download

<details>

<summary>Payload</summary>

```json
{
  "id": "evt_{{uuid-of-event}}", // unique ID for this event
  "type": "event",
  "created_at": "2023-01-01T10:52:11.822-05:00",
  "topic": "screenshot_set.approved",
  "data": {
    "object": {
      "type": "screenshot_set",
      "id": "uuid-of-screenshot-set",
      "campaign_id": "uuid-of-campaign", // ID of the Campaign in Ad Reform
      "created_at": "2023-01-01T10:51:23.961-05:00",
      "status": "approved", // this will always be "approved"
      "instructions": "",   // the special instructions sent in the original request
      "review_notes": "",   // notes from Ad Reform if there was a problem with the screenshot
      "screenshots": [
        {
          "type": "screenshot",
          "id": "uuid-of-screenshot", // ID of the Screenshot in Ad Reform
          "status": "uploaded",
          "image_url": "https://app.adreform.com/path/image.png",
          "device": "Desktop",
          "site": {
            "url": "https://theverge.com",
            "host": "theverge.com"
          },
          "ad": {
            "type": "ad",
            "id": "uuid-of-ad", // ID of the Ad in Ad Reform
            "status": "uploaded",
            "lookup_key": "ad-lookup-key-if-present", // or null
            "name": "name-if-present", // name of the Ad in Ad Reform
            "external_id": null,
            "preview_image_url": "https://app.adreform.com/path/image.png",
            "width": 300,
            "height": 250,
            "html_url": "https://app.adreform.com/path/to/ad/in-webui",
            "media": {
              "type": "image_file" // see Media object in docs for possible values
            },
            // if ad was synced from Xandr, this will be present
            "xandr_creative": {
              "xandr_id": 440600236,
              "name": "ad_reform_300x250",
              "line_item": {
                "xandr_id": 21352428,
                "name": "Example Display-EN_O6X6TSIFL-1261",
                "state": "active"
              }
            },
            // if ad was synced from Google Display & Video 360, this will be present
            "google_dv360_creative": {
              "google_dv360_id": "609196778",
              "display_name": "ad_reform_300x250",
              "line_item": {
                "google_dv360_id": "22170535644",
                "display_name": "My Line Item",
                "entity_status": "ENTITY_STATUS_ACTIVE"
              }
            },
            // if ad was synced from Google Ad Manager, this will be present
            "google_ad_manager_creative": {
              "google_ad_manager_id": 138479065953,
              "name": "ad_reform_300x250",
              "line_item": {
                "google_ad_manager_id": 6742858256,
                "name": "My Line Item",
                "status": "READY"
              }
            },
            // if ad was synced from The Trade Desk, this will be present
            "trade_desk_creative": {
              "trade_desk_id": "ocy4qu03",
              "name": "ad_reform_300x250",
              "ad_group": {
                "trade_desk_id": "3j6rtyu",
                "name": "My Ad Group"
              }
            }
          },
          // if a bucket and key_prefix were specified
          // as `subscribers` in the Screenshot Request
          // we'll include info about the S3 objects here
          "s3_objects": [
            {
              "bucket": "my-bucket-name",   // bucket containing the screenshot
              "key": "key-prefix/uuid.png", // object key for the screenshot
              "status": "uploaded"          // will be "upload_failed" if there was a problem uploading
            }
          ],
          // if the screenshot was requested using a specific frame
          // we'll include frame information here
          "frame": {
            "marker": 1.5,                                          // timestamp in seconds when the frame was captured
            "status": "uploaded",                                   // status of the frame capture
            "image_url": "https://app.adreform.com/path/frame.png"  // URL to the frame image
          }
        },
        {
          "type": "screenshot",
          "id": "uuid",
          "status": "uploaded",
          "image_url": "https://app.adreform.com/path/image2.png",
          "device": "Desktop",
          "site": {
            "url": "https://espn.com",
            "host": "espn.com"
          },
          "ad": {
            "id": "uuid-of-ad",
            "status": "uploaded",
            "lookup_key": "ad-lookup-key-if-present",
            "type": "ad",
            "name": "name-if-present",
            "external_id": null,
            "preview_image_url": "https://app.adreform.com/path/image.png",
            "width": 300,
            "height": 250,
            "html_url": "https://app.adreform.com/path/to/ad/in-webui",
            "media": {
              "type": "image_file"
            },
            "xandr_creative": null,
            "google_dv360_creative": null,
            "google_ad_manager_creative": null,
            "trade_desk_creative": null
          },
          "s3_objects": [
            {
              "bucket": "my-bucket-name",
              "key": "key-prefix/uuid.png",
              "status": "uploaded"
            }
          ],
          "frame": null  // will be null if screenshot is not associated with a frame
        }
      ]
    }
  }
}
```

</details>

## Objects

### `ad`

This object contains data about the `ad` creative. Notable fields include:

#### `status`

| Status             | Description                                             |
| ------------------ | ------------------------------------------------------- |
| uploaded           | The ad creative was captured successfully               |
| not\_found         | We couldn't find the creative asset when loading the ad |
| net\_read\_timeout | We encountered network issues when loading the ad       |
| generic\_error     | We encountered an error when loading the ad             |

***

### `screenshot`

This object contains data about the `screenshot`. Notable fields include:

#### `status`

| Status             | Description                                                                |
| ------------------ | -------------------------------------------------------------------------- |
| uploaded           | The screenshot was generated successfully                                  |
| ad\_not\_captured  | The associated `ad` was not captured so the screenshot cannot be generated |
| net\_read\_timeout | We encountered network issues when generating the screenshot               |
| no\_slots\_found   | The requested site had no ad slots matching the associated `ad` dimentions |
| site\_unreachable  | The requested site was not reachable                                       |
| ssl\_error         | We encountered an SSL error when generating the screenshot                 |
| generic\_error     | We encountered an error when capturing the screenshot                      |

#### `frame`

This object contains data about which `frame` was specified for the screenshot. Notable fields include:

**`status`**

| Status   | Description                         |
| -------- | ----------------------------------- |
| uploaded | The frame was captured successfully |
| failed   | The frame capture failed            |
| pending  | The frame is waiting to be captured |

The `frame` field will be:

* Present with frame information when the screenshot is associated with a specific frame
* `null` when the screenshot is not associated with a frame
