This storefront is a working reference merchant. Its server creates SharePay checkout sessions with an API key and fulfils orders from signed webhooks — the same four steps any merchant follows. Here’s the whole integration.
Create a merchant in the SharePay Developer dashboard. It issues an API key (authenticates your checkout calls) and a webhook signing secret (verifies inbound events). Keep both server-side — this shop reads them from SHAREPAY_API_KEY and SHAREPAY_WEBHOOK_SECRET and never ships them to the browser.
In the dashboard, set your allowed return_url domain (here shop.share-pay.co.uk) and your webhook endpoint (https://shop.share-pay.co.uk/api/webhooks/sharepay). SharePay only redirects back to and posts events to registered destinations.
When the shopper hits Pay with SharePay, your server computes the total from your own catalogue (never trust a client-supplied amount) and creates a session. Redirect the shopper to the returned checkout_url.
// lib/sharepay.ts — runs on YOUR server, never the browser
const res = await fetch(`${SHAREPAY_BASE_URL}/api/merchants/checkout/sessions`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-merchant-api-key": SHAREPAY_API_KEY, // secret, server-only
},
body: JSON.stringify({
amount: 26.0, // major units (£), computed from YOUR catalogue
order_reference: "SHOP-...",
return_url: "https://shop.share-pay.co.uk/success",
}),
});
const { checkout_url } = await res.json();
// redirect the shopper to checkout_urlSharePay posts checkout_split.* events as your split progresses. Read the raw request body, verify the HMAC-SHA256 signature against your secret, then update the order. Fulfil on checkout_split.paid.
// lib/sharepay.ts — verify every webhook before trusting it
export function verifyWebhookSignature(rawBody, header, nowSec) {
const { t, v1 } = parse(header); // "t=<unix>,v1=<hex>"
if (Math.abs(nowSec - t) > 300) return false; // reject stale (>5 min)
const expected = crypto
.createHmac("sha256", SHAREPAY_WEBHOOK_SECRET)
.update(`${t}.${rawBody}`) // sign timestamp + RAW body
.digest("hex");
return timingSafeEqual(v1, expected); // constant-time compare
}