Promotion Notifications via Webhooks

Promotion Event Callbacks (Webhooks)

Overview

The system sends callbacks to partners/subscribers when a promotion is created, edited, started, or expired, or when its budget crosses key milestones. This lets subscribers receive updates in near real time instead of polling the Promotion GET APIs.


Onboarding Steps

StepDescription
Provide a public HTTPS endpointShare the callback URL where BHN should send promotion event notifications.
Set up a callback listenerImplement a listener that can receive and process callback messages from BHN.
Verify callbacks using the shared API keyEvery callback is signed with an HMAC-SHA256 signature computed using the API key shared during onboarding (see Authentication).
Choose the events to subscribe toConfirm the promotion events you want to receive (see Subscribable Events).
Provide a failure-notification emailIf delivery fails after all retries, a notification email is sent to this address.

Once this information is provided, the onboarding team configures the notification setup in Hawkeye. After configuration, run a validation test and share the results with the onboarding team.


Subscribable Events

Event TypeDescription
PROMO_CREATIONA new promotion is created.
PROMO_EDITAn existing promotion is updated.
PROMO_STARTEDThe promotion's startDate is reached and it becomes active.
PROMO_EXPIREDThe promotion's expirationDate is reached and it becomes inactive.
PROMO_BUDGET_UTILIZATIONBudget usage crosses a milestone (~75%, ~85%, ~95%). Each milestone fires only once.
PROMO_BUDGET_THRESHOLD_REACHEDBudget usage reaches the tenant-configured maximum threshold percentage.
PROMO_BUDGET_THRESHOLD_REACHED_POST_OVERDRAFTFor overdraft-enabled tenants: budget usage reaches the overdraft threshold after the primary budget is exceeded.
PROMO_BUDGET_EXHAUSTEDBudget consumption reaches 100% of the promotion budget.

Only subscribed and enabled events are sent. Every callback contains the full promotion payload (PromotionDto), regardless of event type.


Payload Field Reference (PromotionDto)

Default values: When a field is not applicable to a given event, boolean fields default to false, integer/decimal fields default to 0, and string/object fields are set to null. Fields are never omitted from the payload. Exceptions are noted in the field descriptions (for example, tier bounds in discountTiers[]).

FieldTypeDescription
eventTypestringThe callback event type (see Subscribable Events).
webhookIdUUIDUnique event ID; identical across retries. Use it to filter duplicates.
promotionIdstringUnique promotion ID generated by BHN.
promoCodestringVoucher/promotion code.
namestringPromotion name.
descriptionstringPromotion description.
promotionTypestringCAMPAIGN / PROMOTION / VOUCHER.
promotionEffectstringDISCOUNT / BOGO / SHIPPING.
discountTypestringPERCENTAGE / FIXED / FLAT / UNIT.
discountEffectstringAPPLY_TO_ORDER / APPLY_TO_ITEMS.
percentOffdecimalDiscount percentage (when discountType is PERCENTAGE).
amountOffdecimalDiscount amount (when discountType is FIXED or FLAT).
isActivebooleanWhether the promotion is currently active.
startDateISO-8601Promotion start date/time (UTC).
expirationDateISO-8601Promotion end date/time (UTC).
promotionProductsarrayProducts the promotion applies to, as { productId, productName }.
bogoProductobjectBOGO "Y" product details. Populated when promotionEffect is BOGO. See bogoProduct.
tieredDiscountobjectStructured tiered discount. Populated for tiered percent-off promotions. See tieredDiscount.
termsAndConditionsstringFree-text terms and conditions.
minimumCardValuedecimalMinimum card value the promotion applies to.
maximumCardValuedecimalMaximum card value the promotion applies to.
equalsCardValuedecimalExact card value the promotion applies to.
maximumDiscountAmountdecimalMaximum discount amount that can be granted.
maxQuantityPerOrderintegerMaximum promotion applications per order.
maxQuantityPerCustomerPerCampaignintegerMaximum promotion applications per customer across the campaign.
totalBudgetAmountdecimalTotal configured budget.
utilizedBudgetAmountdecimalBudget amount consumed so far.
remainingBudgetPercentagedecimalRemaining budget percentage (100 − utilized percentage).
isBudgetExhaustedbooleantrue once budget utilization reaches 100%.
totalRedemptionQuantitydecimalRedemption cap, if configured.
utilizedRedemptionQuantitydecimalRedemptions consumed so far.
remainingRedemptionPercentagedecimalRemaining redemption percentage (100 − utilized percentage).
isRedemptionLimitReachedbooleantrue once the redemption cap is reached.

bogoProduct

FieldTypeDescription
pricedecimalBOGO "Y" product price.
errorMessagestringError message if BOGO product resolution failed.
bogoProductDataInfoobjectBOGO product details. See bogoProductDataInfo.

bogoProductDataInfo

FieldTypeDescription
namestringProduct name.
brandNamestringBrand name.
provisioningTypestringProvisioning type, e.g. DIGITAL.
bogoProductImagesarrayList of { productId, images[] }, where each image is { frontImage, imageSize, altText }.

tieredDiscount

FieldTypeDescription
discountTypestringTypically PERCENTAGE.
discountTiersarrayOrdered list of tiers. See discountTiers[].

discountTiers[]

FieldTypeDescription
tierIndexinteger1-based tier index, in evaluation order.
minOrderAmountdecimalInclusive lower bound of the order-amount range. Nullable for metadata-only tiers.
maxOrderAmountdecimalInclusive upper bound of the order-amount range. Nullable for the top tier.
percentOffdecimalStatic discount percentage for this tier.
displayLabelstringDisplay label, e.g. "10% Off".
displayConditionstringHuman-readable condition, e.g. "Orders up to $149.99", "user_group = NEW".

Authentication

Every callback is signed with HMAC-SHA256. The following headers are sent on every request:

HeaderDescription
X-SignatureLowercase hex HMAC-SHA256 of <X-Timestamp>.<raw_body>, using the API key as the secret.
X-TimestampUnix timestamp (seconds) when the signature was computed.
x-api-key / api-keyAPI key shared during onboarding.
tenant-idYour BHN tenant ID.
correlation-idUUID, identical across retries of the same event.
Content-Typeapplication/json

To verify a callback:

  1. Recompute the HMAC-SHA256 of <X-Timestamp>.<raw_body> using the API key, and compare it with X-Signature. Use the raw request body exactly as received (before any JSON parsing), and use a constant-time comparison.
  2. Reject the request if X-Timestamp differs from the current time by more than 300 seconds (5-minute replay window).

Failure & Retry Handling

  • Delivery is attempted up to 3 times with exponential backoff on any 4xx/5xx response, timeout, or IO error.
  • All attempts share the same webhookId and correlation-id, so the receiver can safely deduplicate.
  • After the final failed attempt, the system persists a failure record and sends a failure-notification email to the tenant-configured contact.

Sample Callback Payloads

PROMO_CREATION / PROMO_EDIT

{
  "eventType": "PROMO_CREATION",
  "webhookId": "cb527f85-bd7d-4ea1-b0de-b550bb2ded99",
  "promotionId": "promo_OIgqHwGktuBTApSjmBmW3T4T",
  "promoCode": null,
  "name": "15% Off",
  "description": null,
  "promotionType": "PROMOTION",
  "promotionEffect": "DISCOUNT",
  "discountType": "PERCENTAGE",
  "discountEffect": "APPLY_TO_ITEMS",
  "percentOff": 15,
  "amountOff": 0,
  "isActive": true,
  "startDate": "2026-07-01T04:00:00Z",
  "expirationDate": "2026-08-04T04:00:00Z",
  "promotionProducts": [
    { "productId": "PROD-07675025316", "productName": "Fandango eGift_eGift_USD_$25 to $100" },
    { "productId": "PROD-07675025343", "productName": "Top Golf International eGift_eGift_USD_$20 to $250" }
  ],
  "bogoProduct": null,
  "tieredDiscount": null,
  "termsAndConditions": null,
  "minimumCardValue": 0,
  "maximumCardValue": 0,
  "equalsCardValue": 0,
  "maximumDiscountAmount": 0,
  "maxQuantityPerOrder": 0,
  "maxQuantityPerCustomerPerCampaign": 0,
  "totalBudgetAmount": 0,
  "utilizedBudgetAmount": 0,
  "remainingBudgetPercentage": 0,
  "isBudgetExhausted": false,
  "totalRedemptionQuantity": 0,
  "utilizedRedemptionQuantity": 0,
  "remainingRedemptionPercentage": 0,
  "isRedemptionLimitReached": false
}

PROMO_STARTED / PROMO_EXPIRED

Same schema as above. eventType is PROMO_STARTED or PROMO_EXPIRED, and isActive reflects the new state.

Budget events

Applies to PROMO_BUDGET_UTILIZATION, PROMO_BUDGET_THRESHOLD_REACHED, PROMO_BUDGET_THRESHOLD_REACHED_POST_OVERDRAFT, and PROMO_BUDGET_EXHAUSTED. Same schema, with the budget fields populated:

{
  "eventType": "PROMO_BUDGET_UTILIZATION",
  "webhookId": "b60c17aa-14a2-4b7c-b5cf-2f8a7f19be80",
  "promotionId": "promo_OIgqHwGktuBTApSjmBmW3T4T",
  "promoCode": null,
  "name": "15% Off",
  "description": null,
  "promotionType": "PROMOTION",
  "promotionEffect": "DISCOUNT",
  "discountType": "PERCENTAGE",
  "discountEffect": "APPLY_TO_ITEMS",
  "percentOff": 15,
  "amountOff": 0,
  "isActive": true,
  "startDate": "2026-07-01T04:00:00Z",
  "expirationDate": "2026-08-04T04:00:00Z",
  "promotionProducts": [
    { "productId": "PROD-07675025316", "productName": "Fandango eGift_eGift_USD_$25 to $100" },
    { "productId": "PROD-07675025343", "productName": "Top Golf International eGift_eGift_USD_$20 to $250" }
  ],
  "bogoProduct": null,
  "tieredDiscount": null,
  "termsAndConditions": null,
  "minimumCardValue": 0,
  "maximumCardValue": 0,
  "equalsCardValue": 0,
  "maximumDiscountAmount": 0,
  "maxQuantityPerOrder": 0,
  "maxQuantityPerCustomerPerCampaign": 0,
  "totalBudgetAmount": 10000.00,
  "utilizedBudgetAmount": 7502.30,
  "remainingBudgetPercentage": 24.98,
  "isBudgetExhausted": false,
  "totalRedemptionQuantity": 0,
  "utilizedRedemptionQuantity": 0,
  "remainingRedemptionPercentage": 0,
  "isRedemptionLimitReached": false
}

tieredDiscount example (within PROMO_CREATION / PROMO_EDIT)

{
  "tieredDiscount": {
    "discountType": "PERCENTAGE",
    "discountTiers": [
      {
        "tierIndex": 1,
        "minOrderAmount": 300,
        "maxOrderAmount": null,
        "percentOff": 20,
        "displayLabel": "20% Off",
        "displayCondition": "user_group = NEW, Orders $300 and above"
      },
      {
        "tierIndex": 2,
        "minOrderAmount": null,
        "maxOrderAmount": 299.99,
        "percentOff": 15,
        "displayLabel": "15% Off",
        "displayCondition": "user_group = NEW, Orders up to $299.99"
      },
      {
        "tierIndex": 3,
        "minOrderAmount": 300,
        "maxOrderAmount": null,
        "percentOff": 15,
        "displayLabel": "15% Off",
        "displayCondition": "user_group = REGISTERED, Orders $300 and above"
      },
      {
        "tierIndex": 4,
        "minOrderAmount": null,
        "maxOrderAmount": 299.99,
        "percentOff": 10,
        "displayLabel": "10% Off",
        "displayCondition": "user_group = REGISTERED, Orders up to $299.99"
      },
      {
        "tierIndex": 5,
        "minOrderAmount": 300,
        "maxOrderAmount": null,
        "percentOff": 10,
        "displayLabel": "10% Off",
        "displayCondition": "user_group = GUEST, Orders $300 and above"
      },
      {
        "tierIndex": 6,
        "minOrderAmount": null,
        "maxOrderAmount": 299.99,
        "percentOff": 5,
        "displayLabel": "5% Off",
        "displayCondition": "user_group = GUEST, Orders up to $299.99"
      },
      {
        "tierIndex": 7,
        "minOrderAmount": null,
        "maxOrderAmount": null,
        "percentOff": 5,
        "displayLabel": "5% Off",
        "displayCondition": "user_group = any other"
      }
    ]
  }
}

bogoProduct example (within PROMO_CREATION / PROMO_EDIT)

{
  "bogoProduct": {
    "price": 30.0,
    "errorMessage": null,
    "bogoProductDataInfo": {
      "name": "AMC Popcorn eGift FRC",
      "brandName": "AMC Popcorn eGift FRC",
      "provisioningType": "DIGITAL",
      "bogoProductImages": [
        {
          "productId": "YnJuOmM6TUdYUDJTRERONjRENlY5NFpWQU4zOThBS0g6aTo3NjY5Ng==",
          "images": [
            {
              "frontImage": "https://content.blackhawknetwork.com/gcmimages/product/xxlarge/VW0DGS1XD2P3F3K9PS3645DATG_0512202614:58:21.PNG",
              "imageSize": "XXL",
              "altText": "n/a image unavailable"
            },
            {
              "frontImage": "https://content.blackhawknetwork.com/gcmimages/product/large/VW0DGS1XD2P3F3K9PS3645DATG_0512202614:58:21.PNG",
              "imageSize": "LARGE",
              "altText": "n/a image unavailable"
            },
            {
              "frontImage": "https://content.blackhawknetwork.com/gcmimages/product/masterimage/VW0DGS1XD2P3F3K9PS3645DATG_0512202614:58:21.PNG",
              "imageSize": "MASTER_IMAGE",
              "altText": "n/a image unavailable"
            }
          ]
        }
      ]
    }
  }
}

Did this page help you?