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
| Step | Description |
|---|---|
| Provide a public HTTPS endpoint | Share the callback URL where BHN should send promotion event notifications. |
| Set up a callback listener | Implement a listener that can receive and process callback messages from BHN. |
| Verify callbacks using the shared API key | Every callback is signed with an HMAC-SHA256 signature computed using the API key shared during onboarding (see Authentication). |
| Choose the events to subscribe to | Confirm the promotion events you want to receive (see Subscribable Events). |
| Provide a failure-notification email | If 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 Type | Description |
|---|---|
PROMO_CREATION | A new promotion is created. |
PROMO_EDIT | An existing promotion is updated. |
PROMO_STARTED | The promotion's startDate is reached and it becomes active. |
PROMO_EXPIRED | The promotion's expirationDate is reached and it becomes inactive. |
PROMO_BUDGET_UTILIZATION | Budget usage crosses a milestone (~75%, ~85%, ~95%). Each milestone fires only once. |
PROMO_BUDGET_THRESHOLD_REACHED | Budget usage reaches the tenant-configured maximum threshold percentage. |
PROMO_BUDGET_THRESHOLD_REACHED_POST_OVERDRAFT | For overdraft-enabled tenants: budget usage reaches the overdraft threshold after the primary budget is exceeded. |
PROMO_BUDGET_EXHAUSTED | Budget 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)
PromotionDto)Default values: When a field is not applicable to a given event, boolean fields default to
false, integer/decimal fields default to0, and string/object fields are set tonull. Fields are never omitted from the payload. Exceptions are noted in the field descriptions (for example, tier bounds indiscountTiers[]).
| Field | Type | Description |
|---|---|---|
eventType | string | The callback event type (see Subscribable Events). |
webhookId | UUID | Unique event ID; identical across retries. Use it to filter duplicates. |
promotionId | string | Unique promotion ID generated by BHN. |
promoCode | string | Voucher/promotion code. |
name | string | Promotion name. |
description | string | Promotion description. |
promotionType | string | CAMPAIGN / PROMOTION / VOUCHER. |
promotionEffect | string | DISCOUNT / BOGO / SHIPPING. |
discountType | string | PERCENTAGE / FIXED / FLAT / UNIT. |
discountEffect | string | APPLY_TO_ORDER / APPLY_TO_ITEMS. |
percentOff | decimal | Discount percentage (when discountType is PERCENTAGE). |
amountOff | decimal | Discount amount (when discountType is FIXED or FLAT). |
isActive | boolean | Whether the promotion is currently active. |
startDate | ISO-8601 | Promotion start date/time (UTC). |
expirationDate | ISO-8601 | Promotion end date/time (UTC). |
promotionProducts | array | Products the promotion applies to, as { productId, productName }. |
bogoProduct | object | BOGO "Y" product details. Populated when promotionEffect is BOGO. See bogoProduct. |
tieredDiscount | object | Structured tiered discount. Populated for tiered percent-off promotions. See tieredDiscount. |
termsAndConditions | string | Free-text terms and conditions. |
minimumCardValue | decimal | Minimum card value the promotion applies to. |
maximumCardValue | decimal | Maximum card value the promotion applies to. |
equalsCardValue | decimal | Exact card value the promotion applies to. |
maximumDiscountAmount | decimal | Maximum discount amount that can be granted. |
maxQuantityPerOrder | integer | Maximum promotion applications per order. |
maxQuantityPerCustomerPerCampaign | integer | Maximum promotion applications per customer across the campaign. |
totalBudgetAmount | decimal | Total configured budget. |
utilizedBudgetAmount | decimal | Budget amount consumed so far. |
remainingBudgetPercentage | decimal | Remaining budget percentage (100 − utilized percentage). |
isBudgetExhausted | boolean | true once budget utilization reaches 100%. |
totalRedemptionQuantity | decimal | Redemption cap, if configured. |
utilizedRedemptionQuantity | decimal | Redemptions consumed so far. |
remainingRedemptionPercentage | decimal | Remaining redemption percentage (100 − utilized percentage). |
isRedemptionLimitReached | boolean | true once the redemption cap is reached. |
bogoProduct
bogoProduct| Field | Type | Description |
|---|---|---|
price | decimal | BOGO "Y" product price. |
errorMessage | string | Error message if BOGO product resolution failed. |
bogoProductDataInfo | object | BOGO product details. See bogoProductDataInfo. |
bogoProductDataInfo
bogoProductDataInfo| Field | Type | Description |
|---|---|---|
name | string | Product name. |
brandName | string | Brand name. |
provisioningType | string | Provisioning type, e.g. DIGITAL. |
bogoProductImages | array | List of { productId, images[] }, where each image is { frontImage, imageSize, altText }. |
tieredDiscount
tieredDiscount| Field | Type | Description |
|---|---|---|
discountType | string | Typically PERCENTAGE. |
discountTiers | array | Ordered list of tiers. See discountTiers[]. |
discountTiers[]
discountTiers[]| Field | Type | Description |
|---|---|---|
tierIndex | integer | 1-based tier index, in evaluation order. |
minOrderAmount | decimal | Inclusive lower bound of the order-amount range. Nullable for metadata-only tiers. |
maxOrderAmount | decimal | Inclusive upper bound of the order-amount range. Nullable for the top tier. |
percentOff | decimal | Static discount percentage for this tier. |
displayLabel | string | Display label, e.g. "10% Off". |
displayCondition | string | Human-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:
| Header | Description |
|---|---|
X-Signature | Lowercase hex HMAC-SHA256 of <X-Timestamp>.<raw_body>, using the API key as the secret. |
X-Timestamp | Unix timestamp (seconds) when the signature was computed. |
x-api-key / api-key | API key shared during onboarding. |
tenant-id | Your BHN tenant ID. |
correlation-id | UUID, identical across retries of the same event. |
Content-Type | application/json |
To verify a callback:
- Recompute the HMAC-SHA256 of
<X-Timestamp>.<raw_body>using the API key, and compare it withX-Signature. Use the raw request body exactly as received (before any JSON parsing), and use a constant-time comparison. - Reject the request if
X-Timestampdiffers 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
webhookIdandcorrelation-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
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
PROMO_STARTED / PROMO_EXPIREDSame 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 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 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"
}
]
}
]
}
}
}Updated 23 days ago
