Articles on: Webhook API

Overview

Push loyalty events from BLOY to your systems in real time, instead of polling for changes.
Register an HTTPS endpoint against a topic, and BLOY sends it a signed POST the moment something happens — a customer earns points, reaches a new VIP tier, or claims a referral reward.

Getting started

API base URL:https://api.bloy.io/rest-api/v1


Plan requirements

Webhook API require the BLOY Unlimited plan.


What you can do
  • Subscribe an endpoint to a topic, or to a whole namespace with points/*
  • Verify every delivery with a per-subscription HMAC signature
  • Rotate a signing secret without downtime, with a 24-hour grace period
  • Inspect delivery history and replay any delivery that failed


Prerequisites
  • A publicly reachable HTTPS endpoint accepting POST — see Callback URL requirements
  • Your shop's public API key, from the BLOY app in your Shopify admin
  • A handler that reads the raw request body (a JSON parser breaks signature verification)
  • Somewhere to store the signing secret — it is returned only once

Quickstart


1. Register a subscription. Save the signingSecret from the response; it is never shown again.

curl -X POST https://api.bloy.io/rest-api/v1/webhooks/subscriptions \
-H "Authorization: Bearer YOUR_PUBLIC_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "topic": "points/earned", "callbackUrl": "https://example.com/hooks/bloy" }'


2. Verify and acknowledge. Check the signature against the raw body, return 2xx immediately, then do the work asynchronously.

const express = require('express');
const crypto = require('crypto');

const app = express();

// Raw body — a JSON parser would break the signature.
app.post('/hooks/bloy', express.raw({ type: 'application/json' }), (req, res) => {
const expected = crypto
.createHmac('sha256', process.env.BLOY_SIGNING_SECRET)
.update(req.body)
.digest('hex');

const a = Buffer.from(expected, 'utf8');
const b = Buffer.from(req.get('X-Bloy-Hmac-Sha256') || '', 'utf8');
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return res.status(401).send('invalid signature');
}

res.sendStatus(200); // acknowledge first
queue.push({ topic: req.get('X-Bloy-Topic'), event: JSON.parse(req.body.toString('utf8')) });
});

Updated on: 26/08/2026

Was this article helpful?

Share your feedback

Cancel

Thank you!