Signing requests
Sign the exact bytes you send. Serialise the JSON once, sign that string, and send that same string as the body.
Keep the signing secret on your server. Never put it in front-end code. Rotate it by contacting support if it leaks.
Node.js / TypeScript
easyvial-orders.ts — drop-in server helper
import { createHmac, randomUUID } from "node:crypto";
const STORE = process.env.EASYVIAL_STORE!; // your slug
const SECRET = process.env.EASYVIAL_SIGNING_SECRET!; // from Store setup
export type OrderStatus =
| "pending" | "processing" | "on-hold" | "completed" | "cancelled" | "refunded" | "failed";
export interface AppOrder {
id: string;
orderKey: string;
status: OrderStatus;
total: number;
refundedTotal?: number;
currency?: string;
utmSource?: string | null;
utmMedium?: string | null;
utmCampaign?: string | null;
createdAt?: Date;
}
export async function reportOrder(o: AppOrder, attempt = 1): Promise<void> {
const body = JSON.stringify({
id: o.id,
order_key: o.orderKey,
status: o.status,
total: o.total.toFixed(2),
refunded_total: (o.refundedTotal ?? 0).toFixed(2),
currency: o.currency ?? "USD",
utm_source: o.utmSource ?? null,
utm_medium: o.utmMedium ?? null,
utm_campaign: o.utmCampaign ?? null,
created_at: (o.createdAt ?? new Date()).toISOString(),
});
const signature = createHmac("sha256", SECRET).update(body).digest("hex");
const res = await fetch("https://easyvial.app/api/v1/orders?store=" + STORE, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-EasyVial-Signature": signature,
"X-EasyVial-Delivery-ID": randomUUID(),
},
body,
});
if (res.status >= 500 && attempt < 4) {
await new Promise((r) => setTimeout(r, attempt * 30_000));
return reportOrder(o, attempt + 1);
}
if (!res.ok) throw new Error(`Order API ${res.status}: ${await res.text()}`);
}PHP
<?php
function report_app_order(array $order): void {
$body = json_encode([
'id' => (string) $order['number'],
'order_key' => $order['secret_key'],
'status' => $order['status'], // pending|processing|on-hold|completed|cancelled|refunded|failed
'total' => number_format($order['total'], 2, '.', ''),
'refunded_total' => number_format($order['refunded'] ?? 0, 2, '.', ''),
'currency' => $order['currency'] ?? 'USD',
'created_at' => gmdate('c', $order['created_at']),
], JSON_UNESCAPED_SLASHES);
$sig = hash_hmac('sha256', $body, getenv('EASYVIAL_SIGNING_SECRET'));
$ch = curl_init('https://easyvial.app/api/v1/orders?store=' . getenv('EASYVIAL_STORE'));
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'X-EasyVial-Signature: ' . $sig],
]);
curl_exec($ch);
curl_close($ch);
}Python
import hashlib, hmac, json, os, requests
def report_app_order(order: dict) -> None:
body = json.dumps({
"id": str(order["number"]),
"order_key": order["secret_key"],
"status": order["status"],
"total": f'{order["total"]:.2f}',
"refunded_total": f'{order.get("refunded", 0):.2f}',
"currency": order.get("currency", "USD"),
}, separators=(",", ":"))
sig = hmac.new(os.environ["EASYVIAL_SIGNING_SECRET"].encode(), body.encode(), hashlib.sha256).hexdigest()
requests.post(
"https://easyvial.app/api/v1/orders?store=" + os.environ["EASYVIAL_STORE"],
data=body, # send the exact string you signed
headers={"Content-Type": "application/json", "X-EasyVial-Signature": sig},
timeout=10,
).raise_for_status()Ruby
require "json"; require "net/http"; require "openssl"
def report_app_order(order)
body = { id: order.number.to_s, order_key: order.secret_key, status: order.status,
total: format("%.2f", order.total), currency: order.currency || "USD" }.to_json
sig = OpenSSL::HMAC.hexdigest("SHA256", ENV.fetch("EASYVIAL_SIGNING_SECRET"), body)
uri = URI("https://easyvial.app/api/v1/orders?store=#{ENV.fetch('EASYVIAL_STORE')}")
Net::HTTP.post(uri, body, "Content-Type" => "application/json", "X-EasyVial-Signature" => sig)
endGo
func ReportAppOrder(o Order) error {
body, _ := json.Marshal(map[string]any{
"id": o.Number, "order_key": o.SecretKey, "status": o.Status,
"total": fmt.Sprintf("%.2f", o.Total), "currency": "USD",
})
mac := hmac.New(sha256.New, []byte(os.Getenv("EASYVIAL_SIGNING_SECRET")))
mac.Write(body)
req, _ := http.NewRequest("POST", "https://easyvial.app/api/v1/orders?store="+os.Getenv("EASYVIAL_STORE"), bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-EasyVial-Signature", hex.EncodeToString(mac.Sum(nil)))
_, err := http.DefaultClient.Do(req)
return err
}Verifying your signature
Getting 401 invalid_signature? Sign and send the identical string. Frameworks that re-encode JSON (adding spaces, reordering keys) or middleware that rewrites the body will break the signature.