Skip to main content

Webhooks

Webhooks allow you to receive real-time server-side notifications about transactions and catalog changes. Unlike SDK events (which are client-side), webhooks are server-to-server callbacks that are reliable and cannot be missed due to client disconnections.

Overview

If a partner is interested in receiving real-time callbacks from Hubble, they can share a webhook URL with us. Hubble will send POST requests to this URL for supported events.

All webhooks are signed using an HMAC signature, and partners are expected to verify the request authenticity using the X-Verify header.


Setting Up Webhooks

To enable webhooks, provide the Hubble team with your webhook endpoint URL. Hubble will send POST requests to this URL for supported events. Your endpoint must:

  • Accept POST requests with a JSON body.
  • Return a 200 status code to acknowledge receipt. Any non-200 response will be treated as a delivery failure.
  • Be publicly accessible over HTTPS.

Transaction Webhooks

Triggered whenever a transaction occurs via the Hubble SDK. Different webhook payloads are sent based on the order status.

Order StatusWhen It FiresVoucher Data
COMPLETEDGift card successfully generatedIncludes voucher codes, PINs, and validity dates
FAILEDOrder failed after paymentEmpty vouchers array; coins are reversed if applicable
REVERSEDManual reversal by Hubble teamEmpty vouchers array; refund has been processed

COMPLETED Status

Triggered by: Successful redemption

{
"transactionReferenceId": "01HRVY38NCAANDYPN3WVGJCDSG",
"partnerCoinTransactionId": "01HZ123EXAMPLE456789",
"partnerCoinReversalTransactionId": null,
"amount": 1000.0,
"discountAmount": 50.0,
"timestamp": "2024-01-15T14:30:22",
"userId": "01HRW71319JNKJS6D3Q85X780R",
"vouchers": [
{
"id": "01HZ46Q0P0242STC8TBV926M8C",
"brandId": "01JRSY42BBMKC2YS9TXMYSYN83",
"brandName": "Swiggy",
"cardType": "CARD_NUMBER_SECURED",
"cardNumber": "5333644476668777",
"cardPin": "123456",
"validTill": "2025-04-04",
"amount": 1000.0,
"shareImageUrl": "https://gullak-assets.s3.ap-south-1.amazonaws.com/share-wa-brand-images/Swiggy.jpg",
"brandRetailModes": ["online", "offline", "app", "website"]
}
],
"brandName": "Swiggy",
"orderStatus": "COMPLETED"
}

FAILED Status

Triggered when orders fail.

{
"transactionReferenceId": "01HRVY38NCAANDYPN3WVGJCDSG",
"partnerCoinTransactionId": "01HZ123EXAMPLE456789",
"partnerCoinReversalTransactionId": "01HZ456REVERSAL789012",
"amount": 1000.0,
"discountAmount": 50.0,
"timestamp": "2024-01-15T14:35:10",
"userId": "01HRW71319JNKJS6D3Q85X780R",
"vouchers": [],
"brandName": "Swiggy",
"orderStatus": "FAILED"
}

REVERSED Status

Triggered during manual reversals.

{
"transactionReferenceId": "01HRVY38NCAANDYPN3WVGJCDSG",
"partnerCoinTransactionId": "01HZ123EXAMPLE456789",
"partnerCoinReversalTransactionId": "01HZ456REVERSAL789012",
"amount": 1000.0,
"discountAmount": 50.0,
"timestamp": "2024-01-15T15:00:00",
"userId": "01HRW71319JNKJS6D3Q85X780R",
"vouchers": [],
"brandName": "Swiggy",
"orderStatus": "REVERSED"
}
note
  • brandRetailModes can be null and must be treated as flexible tags, not a strict enum.
  • partnerCoinReversalTransactionId and partnerCoinTransactionId will be null always if coin module is not integrated.

Brand Update Webhooks

Brand webhooks notify you of catalog changes so you can update your local catalog or UI accordingly:

EventDescription
USER_DISCOUNT_UPDATEDDiscount percentage changed for a brand
NEW_BRAND_CREATEDA new brand has been added to the catalog
BRAND_DISABLEDA brand has been removed or disabled
PARTNER_DISCOUNT_UPDATEDYour partner-specific discount has been updated (includes validity dates)

USER_DISCOUNT_UPDATED

{
"event": "USER_DISCOUNT_UPDATED",
"details": {
"id": "01GMAVS2CHXR0XP1BZSTA9A44K",
"name": "Amazon shopping",
"discountPercentage": 5
}
}

NEW_BRAND_CREATED

{
"event": "NEW_BRAND_CREATED",
"details": {
"id": "01GMAVS2CHXR0XP1BZSTA9A44K",
"name": "Amazon shopping",
"discountPercentage": 2
}
}

BRAND_DISABLED

{
"event": "BRAND_DISABLED",
"details": {
"id": "01GMAVS2CHXR0XP1BZSTA9A44K",
"name": "Amazon shopping"
}
}

PARTNER_DISCOUNT_UPDATED

{
"event": "PARTNER_DISCOUNT_UPDATED",
"details": {
"brandId": "01HFV6NGBFGATYAKZ3AKASJVM9",
"subventionPercentage": 8,
"validFrom": "2025-12-23",
"validTo": "2026-02-28"
}
}

Webhook Authentication & Security

All webhook requests include an X-Verify header containing an HMAC-SHA256 signature of the request body, encoded in Base64. You must verify this signature before processing any webhook to ensure the request genuinely came from Hubble.

Verification steps:

  1. Extract the X-Verify header from the incoming request
  2. Read the raw request body as a string (before any JSON parsing or whitespace modification).
  3. Compute HMAC-SHA256 of the raw body using your webhook secret key, then Base64-encode the result.
  4. Compare your computed signature with the X-Verify header. Only process the request if they match exactly.

Example: Node.js

const crypto = require("crypto");

function verifyWebhook(rawBody, signatureHeader, secret) {
const hmac = crypto.createHmac("sha256", secret);
hmac.update(rawBody);
const expected = hmac.digest("base64");
return expected === signatureHeader;
}

// In your webhook handler:
app.post("/hubble-webhook", (req, res) => {
const rawBody = req.rawBody; // Must be the raw string, not parsed JSON
const signature = req.headers["x-verify"];
if (!verifyWebhook(rawBody, signature, WEBHOOK_SECRET)) {
return res.status(401).send("Invalid signature");
}
// Process the webhook...
res.status(200).send("OK");
});

Example: Kotlin

import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import java.util.Base64

private fun generateSignature(payload: String, secret: String): String {
val algorithm = "HmacSHA256"
val secretKeySpec = SecretKeySpec(secret.toByteArray(), algorithm)
val mac = Mac.getInstance(algorithm).apply { init(secretKeySpec) }
val signatureBytes = mac.doFinal(payload.toByteArray())
return Base64.getEncoder().encodeToString(signatureBytes)
}

fun verifyWebhook(requestBody: String, signatureHeader: String, secret: String): Boolean {
val expectedSignature = generateSignature(requestBody, secret)
return expectedSignature == signatureHeader
}

Important Identifiers

  • transactionReferenceId: Use this as the unique transaction identifier for all webhook callbacks. This ID remains consistent throughout the transaction lifecycle.
  • userId: This corresponds to the customer_id that was provided to Hubble during initialization.

Security Best Practices

  1. Always verify the webhook signature before processing any request
  2. Never log or expose your webhook secret key
  3. Use HTTPS for your webhook endpoint
  4. Implement rate limiting to prevent abuse
  5. Validate all incoming data before processing

IP Whitelisting (Optional)

For additional security, you can whitelist Hubble’s production IP addresses on your firewall:

  • 35.200.156.199
  • 34.47.147.244
  • 34.14.138.52
Common Webhook Issues
  1. Signature validation failure: If you are parsing the JSON body before computing the signature, the whitespace or field ordering may differ from the raw body. Always compute the HMAC on the raw request string, not on a re-serialized JSON object.
  2. Webhook endpoint returning 401/400/500: There can be webhook delivery failures because their endpoint had authentication middleware (like a JWT check) that blocks the Hubble request. Your webhook endpoint should authenticate using the X-Verify signature, not your app's general auth middleware.
  3. Missing FAILED status handling: If you only handle COMPLETED webhooks and ignore FAILED. When an order fails, you may need to update your UI, refund coins, or notify the user. Always handle both COMPLETED and FAILED statuses.
  4. Webhooks not being received: Confirm your webhook URL has been registered with the Hubble team for the correct environment (staging vs. production). This is a separate registration from the SSO URL.
Webhook Idempotency

Hubble may retry webhook delivery if your endpoint does not return a 200 response. Implement idempotency on your webhook handler using the transactionReferenceId. If you have already processed a webhook for a given transactionReferenceId, return 200 without reprocessing.