API and price webhook

Last updated 10 November 2026

Your site is not on a platform we list? Any stack works. There are three ways to get a CostCal Pro price into your own code. Use one or all.

1. Price webhook (we push to you)

In the app, open Stores, choose Custom site and enter an https address on your site. We show you a signing secret once. When you press Send price to my site (Premium and above), we POST a JSON message to that address. Your code checks the signature, then updates your own database.

POST /hooks/costcal HTTP/1.1
Content-Type: application/json
X-CostCal-Event: price.update
X-CostCal-Timestamp: 1790000000
X-CostCal-Signature: 9b1c...e4 (hex HMAC-SHA256 of "1790000000." + body)
X-CostCal-Delivery: 7f0e6d6e-6f0c-4b0c-9f0a-1d2e3f4a5b6c

{"event":"price.update","sku":"MUG-01","name":"Mug","price":"14.29","currency":"USD","calculationId":"c5c0..."}

Node.js (Express)

import crypto from "node:crypto";
import express from "express";

const app = express();

// Keep the raw body: the signature covers the exact bytes we sent.
app.post("/hooks/costcal", express.raw({ type: "application/json" }), (req, res) => {
  const ts = req.header("X-CostCal-Timestamp") ?? "";
  const sig = req.header("X-CostCal-Signature") ?? "";
  const body = req.body.toString("utf8");

  if (!/^\d{9,12}$/.test(ts) || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return res.sendStatus(400);

  const want = crypto.createHmac("sha256", process.env.COSTCAL_SECRET).update(ts + "." + body).digest("hex");
  const a = Buffer.from(sig, "hex");
  const b = Buffer.from(want, "hex");
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return res.sendStatus(401);

  const event = JSON.parse(body); // { event, sku, name, price, currency, calculationId }
  if (event.event === "price.update") {
    // update your own database here, for example: UPDATE products SET price = event.price WHERE sku = event.sku
  }
  res.sendStatus(200);
});

app.listen(3000);

PHP

<?php
// hooks/costcal.php
$body = file_get_contents('php://input');                 // raw body
$ts   = $_SERVER['HTTP_X_COSTCAL_TIMESTAMP'] ?? '';
$sig  = $_SERVER['HTTP_X_COSTCAL_SIGNATURE'] ?? '';

if (!ctype_digit($ts) || abs(time() - (int) $ts) > 300) { http_response_code(400); exit; }

$want = hash_hmac('sha256', $ts . '.' . $body, getenv('COSTCAL_SECRET'));
if (!hash_equals($want, $sig)) { http_response_code(401); exit; }

$event = json_decode($body, true);                        // event, sku, name, price, currency, calculationId
if (($event['event'] ?? '') === 'price.update') {
    // update your own database here, for example with a prepared statement keyed on $event['sku']
}
http_response_code(200);

Python (Flask)

import hashlib
import hmac
import json
import os
import time

from flask import Flask, abort, request

app = Flask(__name__)


@app.post("/hooks/costcal")
def costcal():
    body = request.get_data()  # raw bytes
    ts = request.headers.get("X-CostCal-Timestamp", "")
    sig = request.headers.get("X-CostCal-Signature", "")

    if not ts.isdigit() or abs(time.time() - int(ts)) > 300:
        abort(400)

    want = hmac.new(os.environ["COSTCAL_SECRET"].encode(), ts.encode() + b"." + body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(want, sig):
        abort(401)

    event = json.loads(body)  # event, sku, name, price, currency, calculationId
    if event.get("event") == "price.update":
        pass  # update your own database here, keyed on event["sku"]
    return "", 200

2. Pull API (you fetch from us)

Create a personal API key under API keys (Starter and above). Send it as a bearer token. Keys are shown once, stored only as a hash, scoped, and can be revoked at any time.

curl -H "Authorization: Bearer $COSTCAL_KEY" "https://costcalpro.shop/api/v1/prices?sku=MUG-01"
curl -H "Authorization: Bearer $COSTCAL_KEY" "https://costcalpro.shop/api/v1/prices?format=csv" -o prices.csv
curl -H "Authorization: Bearer $COSTCAL_KEY" "https://costcalpro.shop/api/v1/calculations?limit=50"

3. CSV or JSON export

In Stores, the Price list card downloads your prices as CSV or JSON (SKU, name, price, currency). This works on every paid plan. For a stable address that a cron job can fetch, use the pull API with ?format=csv.