CryptMeUp Logo

CryptMeUp

Developer Documentation

Commerce Checkout: Signed HMAC Flow

Use this flow when your webshop calculates the final order amount and wants to open a CryptMeUp checkout link that is tamper-protected.

What This Flow Solves

A synced checkout link prevents shoppers from changing amount or order metadata in the browser.

CryptMeUp validates your HMAC signature and timestamp before loading the payment page.

  • Integrity: order amount and metadata cannot be modified client-side.
  • Replay protection: links expire when timestamp is outside the allowed time window.
  • Deterministic verification: query parameters are sorted before signing.

Prerequisites

Create a Synced transaction and store its identifier (UUID).

Set CRYPT_ME_UP_SECRET on your backend (never expose it in frontend code).

Use your backend to generate the signed checkout URL.

Checkout URL Shape

https://cryptmeup.com/{locale}/pay/transaction/{transaction_identifier}?order_id=...&user_id=...&description=...&description_long=...&amount_minor=...&fiat=...&customer_email=...&customer_name=...&ts=...&sig=sha256=...

Signable Parameters

Send only the documented keys; unknown keys are rejected.

fiat must be valid and amount_minor must be in configured min/max range for that fiat.

  • Required: description, amount_minor, fiat
  • Optional: description_long, order_id, user_id, customer_email, customer_name
  • Timestamp query key: ts
  • Signature query key: sig (format: sha256=<hex>)

HMAC Construction

Sort parameters alphabetically by key.

Build canonical payload using RFC3986 query encoding.

Sign "{timestamp}.{payload}" with HMAC SHA-256 and your CRYPT_ME_UP_SECRET.

Pseudo Code (Any Backend Language)

params = {
  order_id: "ORDER-100045",
  user_id: "cust_582",
  description: "Order #100045",
  description_long: "Blue hoodie / size L",
  amount_minor: 4999,
  fiat: "USD",
  customer_email: "[email protected]",
  customer_name: "Jane Doe"
}

sort params by key
payload = RFC3986_QUERY(params)
timestamp = unix_time_seconds()
signed_payload = timestamp + "." + payload
signature = "sha256=" + HMAC_SHA256_HEX(secret, signed_payload)

url = "/en/pay/transaction/{identifier}?" + payload + "&ts=" + timestamp + "&sig=" + signature

PHP Example

$params = [
    'order_id' => 'ORDER-100045',
    'user_id' => 'cust_582',
    'description' => 'Order #100045',
    'description_long' => 'Blue hoodie / size L',
    'amount_minor' => 4999,
    'fiat' => 'USD',
    'customer_email' => '[email protected]',
    'customer_name' => 'Jane Doe',
];

ksort($params);
$payload = http_build_query($params, '', '&', PHP_QUERY_RFC3986);
$ts = time();
$signedPayload = $ts.'.'.$payload;
$sig = 'sha256='.hash_hmac('sha256', $signedPayload, env('CRYPT_ME_UP_SECRET'));

$url = "https://cryptmeup.com/en/pay/transaction/{$transactionIdentifier}?{$payload}&ts={$ts}&sig={$sig}";

Server Verification Behavior

For synced transactions, CryptMeUp requires both ts and sig.

CryptMeUp verifies the signature over sorted parameters and rejects invalid signatures.

Current replay window is 5 minutes (300 seconds).

Common Failure Responses

401 This checkout link is missing a valid signature. Please generate a new payment link.
401 Sorry, we couldn’t verify your payment request. Please go back to the checkout page and try again.
401 Your payment session has expired. No worries – please go back and try placing the order again.

Implementation Checklist

Use backend-only signing, never sign in JavaScript.

Always regenerate ts and sig per checkout attempt.

  • Log signed payload, timestamp, and generated URL in your server logs.
  • Keep CRYPT_ME_UP_SECRET in secure env management.
  • Do not include extra query keys outside the allowed DTO fields.
  • Use HTTPS for all storefront and callback flows.