Skip to main content

Payment Service Request Signing

If you choose not to use an API Key, you can authenticate your requests using EIP-191 signatures.

Required Headers (Signature Mode)

  • x-depay-timestamp
  • x-depay-signature

Timestamp Rules

  1. Timestamp must be within ±5 minutes of server time.
  2. Timestamp cannot be reused for the same owner address.

Replay key format:

  • (ownerAddress, timestamp)

Signed Message

The signature input is always:

DePay API Request Signature
<CANONICAL_JSON>

Canonical JSON shape:

{
"action": "invoice.create",
"ownerAddress": "0x...",
"timestamp": 1714200000000,
"payload": {
"chainId": 8453,
"invoiceId": "1000456",
"amount": "12.5",
"webhookUrl": "https://merchant.example.com/webhooks/invoice"
}
}

:::warning Canonical JSON must be byte-identical The signed string is serialized without extra whitespace and with the key order shown above (action, ownerAddress, timestamp, payload). JSON.stringify in JavaScript already produces this form; in Python use json.dumps(obj, separators=(",", ":")). Omit optional payload fields entirely instead of sending null — an omitted key and a null key produce different signatures. :::

Action Values

  • invoice.create
  • invoice.status
  • invoice.delete
  • invoice.list
  • fee.set-meta
  • fee.set-payout-meta

Signing Helper

sign.ts
import { privateKeyToAccount } from "viem/accounts";

const account = privateKeyToAccount(
process.env.OWNER_PRIVATE_KEY as `0x${string}`,
);

export async function sign(
action: string,
payload: Record<string, unknown>,
): Promise<{ signature: string; timestamp: number }> {
const timestamp = Date.now();

const canonical = JSON.stringify({
action,
ownerAddress: account.address,
timestamp,
payload,
});

const signature = await account.signMessage({
message: `DePay API Request Signature\n${canonical}`,
});

return { signature, timestamp };
}

With ethers v6 instead of viem:

sign-ethers.ts
import { Wallet } from "ethers";

const wallet = new Wallet(process.env.OWNER_PRIVATE_KEY as string);

export async function sign(
action: string,
payload: Record<string, unknown>,
): Promise<{ signature: string; timestamp: number }> {
const timestamp = Date.now();

const canonical = JSON.stringify({
action,
ownerAddress: wallet.address,
timestamp,
payload,
});

const signature = await wallet.signMessage(
`DePay API Request Signature\n${canonical}`,
);

return { signature, timestamp };
}

Integration Notes

  • Regenerate the signature whenever the timestamp changes — signature and timestamp always travel as a pair.
  • The signed payload must match what you actually send. For POST requests that is the body minus ownerAddress; for GET/DELETE it is the query params minus ownerAddress.
  • Keep the action value aligned with the endpoint intent (see Action Values).