Xtopay Docs
Authentication

Request Signing

Optional HMAC-SHA256 request signing for extra security on sensitive operations.

Overview

For high-security operations — large payouts, subscription cancellations, refunds — Xtopay supports optional request signing. Signing proves that the request body has not been tampered with in transit, even if your transport layer is compromised.

Signed requests include two additional headers alongside Basic Auth:

HeaderExample
X-Xtopay-Timestamp1716537600 (Unix timestamp)
X-Xtopay-Signaturesha256=a3f8b2...

Signing algorithm

The signature is HMAC-SHA256 over the signing string, keyed with your Client Secret.

Signing string format:

{timestamp}.{HTTP_METHOD}.{path}.{body}
  • timestamp — Unix timestamp in seconds (from X-Xtopay-Timestamp)
  • HTTP_METHOD — uppercase: POST, GET, PATCH, DELETE
  • path — the request path including query string, e.g. /v1/payments
  • body — the raw JSON request body (empty string for GET/DELETE)

Example signing string:

1716537600.POST./v1/refunds.{"payment_id":"pay_abc123","amount":5000}

Implementing signing

import crypto from "crypto";

function signRequest(
  method: string,
  path: string,
  body: string,
  clientSecret: string,
): { timestamp: string; signature: string } {
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const signingString = `${timestamp}.${method.toUpperCase()}.${path}.${body}`;

  const signature = crypto
    .createHmac("sha256", clientSecret)
    .update(signingString)
    .digest("hex");

  return { timestamp, signature: `sha256=${signature}` };
}

// Usage
const body = JSON.stringify({ payment_id: "pay_abc123", amount: 5000 });
const { timestamp, signature } = signRequest(
  "POST",
  "/v1/refunds",
  body,
  process.env.XTOPAY_CLIENT_SECRET!,
);

const encoded = Buffer.from(
  `${process.env.XTOPAY_CLIENT_ID}:${process.env.XTOPAY_CLIENT_SECRET}`
).toString("base64");

fetch("https://api.xtopay.co/v1/refunds", {
  method: "POST",
  headers: {
    "Authorization": `Basic ${encoded}`,
    "Content-Type": "application/json",
    "X-Xtopay-Timestamp": timestamp,
    "X-Xtopay-Signature": signature,
  },
  body,
});

The SDK handles signing automatically when you pass sign: true:

const refund = await xtopay.refunds.create(
  { payment_id: "pay_abc123", amount: 5000 },
  { sign: true },
);

Timestamp tolerance

Xtopay rejects signed requests where the X-Xtopay-Timestamp is more than 5 minutes in the past or future. This prevents replay attacks.

Enabling required signing

You can require signatures on all write operations for your account in Settings → Security → Require request signing. Once enabled, unsigned POST, PATCH, and DELETE requests return 403 signature_required.

Webhook signatures

Webhook payloads delivered to your endpoint are always signed. See Verifying webhook signatures for how to validate them.

On this page