> ## Documentation Index
> Fetch the complete documentation index at: https://developers.loyaltylion.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Introduction

Use webhooks to receive notifications about events that happen in LoyaltyLion,
such as a customer earning points or moving into a new tier. This is more
efficient than polling our API periodically.

## Subscribing to webhooks

Use the [Webhook API](/api-reference/v2/resources/webhooks/create-webhook) to
view, subscribe and unsubscribe to webhooks.

Webhook subscriptions are scoped to the OAuth app (Client ID) that created them.
That is, an OAuth client can only view their own webhook subscriptions. Webhooks
created using API keys are never visible to OAuth clients.

## Receiving webhooks

Once you have subscribed to a webhook, LoyaltyLion issues an HTTP `POST` request
to the specified URL every time that event occurs. Some webhook events may have
a cool-down period, which will be indicated on their documentation page.

<Info>
  Webhooks are delivered with at-least-once delivery semantics. In rare
  situations you may receive duplicate webhooks. If strict exactly-once
  semantics are required, you can deduplicate events based on the `id` field in
  the webhook request body.
</Info>

### Request body

The webhook request body will be a `JSON` object containing webhook metadata and
the event-specific payload.

<Card>
  <ResponseField name="id" type="string">
    Unique ID for this webhook event, which can be used to deduplicate events
  </ResponseField>

  <ResponseField name="topic" type="string">
    The topic of the webhook event
  </ResponseField>

  <ResponseField name="created_at" type="string">
    An `ISO 8601` timestamp representing the date this webhook was sent. This
    may be different from the date the event occurred
  </ResponseField>

  <ResponseField name="payload" type="object">
    The event-specific payload as a JSON object. See the respective page in the
    documentation for each event for a description of its payload.
  </ResponseField>
</Card>

### Verifying requests

The webhooks we deliver have a `x-loyaltylion-hmac-sha256` header, which is a base64 encoded sha256 HMAC of the raw JSON request body and the relevant secret key:

* for webhooks registered with an [an OAuth client](/api-reference/authentication/oauth), use OAuth client secret
* for other webhooks, use your site secret

Example code to verify a webhook:

<CodeGroup>
  ```typescript TypeScript (Express) theme={null}
  import crypto from 'crypto'
  import express from 'express'
  import bodyParser from 'body-parser'

  const LOYALTYLION_SECRET = 'secret'
  const app = express()

  function verifyWebhook(data, signature) {
    const ourSignature = crypto
      .createHmac('sha256', LOYALTYLION_SECRET)
      .update(data)
      .digest('base64')

    return ourSignature === signature
  }

  app.use(
    bodyParser.json({
      verify: (req, res, buffer) => {
        if (!verifyWebhook(buffer, req.get('x-loyaltylion-hmac-sha256'))) {
          throw new Error('Webhook verification failed')
        }
      },
    }),
  )
  ```

  ```ruby Ruby on Rails theme={null}
  class WebhookController < ApplicationController
    LOYALTYLION_SECRET = 'secret'

    def handle_webhook
      verified = verify_webhook(
        request.raw_post,
        request.headers['x-loyaltylion-hmac-sha256'],
      )

      unless verified
        return head(422)
      end

      # handle the webhook
    end

    def verify_webhook(body, hmac)
      digest = OpenSSL::Digest.new('sha256')
      our_hmac = Base64.encode64(
        OpenSSL::HMAC.digest(digest, LOYALTYLION_SECRET, body),
      ).strip

      hmac == our_hmac
    end
  end
  ```

  ```php PHP theme={null}
  define("LOYALTYLION_SECRET", "abc");

  function verify_webhook($data, $hmac) {
    $our_hmac = base64_encode(
      hash_hmac("sha256", $data, LOYALTYLION_SECRET, true)
    );
    return $our_hmac == $hmac;
  }

  $verified = verify_webhook(
    file_get_contents("php://input"),
    $_SERVER["X-LOYALTYLION-HMAC-SHA256"]
  );

  if ($verified) {
    // handle webhook
  }
  ```
</CodeGroup>

## Responding to webhooks

Your app must acknowledge it received the webhook event by responding with a
`200` status code within 5 seconds.

When we see a response status outside the 2xx range, or a response isn't
received within the 5s timeout, that delivery is considered failed.

### Webhook retries

Failed webhook events are retried with an exponential backoff until a success
response is received or the webhook subscription is removed:

* After 5 failed attempts, you receive an email alert
  * For OAuth subscriptions, we'll email the address you used to register the
    OAuth app. For other kinds of webhook subscriptions, we'll email the owner
    of the LoyaltyLion account
* After 30 failed attempts, the webhook subscription is removed and you are
  notified again
