Use your gp_live_… API token for API calls and the separate whsec_… project secret for webhook verification.
createInvoice with curl
curl -X POST https://app.grampaybot.com/api/createInvoice \
-H 'GramPay-API-Token: gp_live_xxx' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: order_184' \
-d '{"amount":"10.00","public_description":"Access for 30 days","payload":"order_184"}'
Node.js
const res = await fetch('https://app.grampaybot.com/api/createInvoice', {
method: 'POST',
headers: {'GramPay-API-Token': process.env.GRAMPAY_TOKEN,
'Content-Type': 'application/json', 'Idempotency-Key': `order_${orderId}`},
body: JSON.stringify({amount: '10.00', payload: `order_${orderId}`}),
});
const data = await res.json();
if (!data.ok) throw new Error(data.error?.code || data.error);
app.post('/webhooks/grampay', express.raw({type: 'application/json'}), (req, res) => {
const expected = 'sha256=' + crypto.createHmac('sha256', process.env.GRAMPAY_WEBHOOK_SECRET)
.update(req.body).digest('hex');
const given = req.headers['grampay-signature'] || '';
if (given.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected)))
return res.sendStatus(401);
const event = JSON.parse(req.body);
// Handle idempotently; webhook_test requires no business action.
res.sendStatus(200);
});
Python
res = requests.post("https://app.grampaybot.com/api/createInvoice",
headers={"GramPay-API-Token": os.environ["GRAMPAY_TOKEN"],
"Idempotency-Key": f"order_{order_id}"},
json={"amount": "10.00", "payload": f"order_{order_id}"}, timeout=10)
res.raise_for_status()
data = res.json()
raw = request.get_data()
expected = "sha256=" + hmac.new(SECRET, raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(request.headers.get("GramPay-Signature", ""), expected):
abort(401)
Go
mac := hmac.New(sha256.New, []byte(os.Getenv("GRAMPAY_WEBHOOK_SECRET")))
mac.Write(rawBody)
expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(r.Header.Get("GramPay-Signature")), []byte(expected)) {
w.WriteHeader(http.StatusUnauthorized); return
}
PHP
$raw = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $raw, getenv('GRAMPAY_WEBHOOK_SECRET'));
$given = $_SERVER['HTTP_GRAMPAY_SIGNATURE'] ?? '';
if (!hash_equals($expected, $given)) { http_response_code(401); exit; }
Production checklist
- Send a stable
Idempotency-Keywith every create request. - Verify HMAC over the raw body and reject invalid signatures.
- Return
2xxfor a validwebhook_testwithout fulfilment. - Deduplicate
invoice_paidbefore granting access or shipping goods. - Alert on the exceptional
402 account_not_serviceableresponse.