Skip to main content
The signer is a server-side HTTP endpoint that receives a SignerRequest from the Deposit SDK, creates a signed payment link, and returns a SignerResponse. This is the core security mechanism. It ensures every payment was authorized by your server.

Request format

The Deposit SDK sends a POST request with this JSON body to your signer URL: Example request body:

Implementation steps

Your endpoint must perform these steps in order:

1. Validate the request

Return HTTP 400 with an error message for any validation failure.
Your signer should allowlist only Blink-supported destination chainId and token combinations. See Supported Networks and Wallets for the current routing catalog.

2. Verify destination ownership

Before signing, confirm that the authenticated user actually controls the destination address. Without this check, a malicious caller could submit someone else’s wallet address and direct funds to an account they don’t own.
When the request carries destinations, run this check over every entry in the array, not just the first. A signer that verifies one address and signs three has authorized deposits to two addresses it never checked.
Your signer should look up the user’s wallets via your auth provider and verify the requested address is among them. Here is an example using Privy as the auth provider:
In your route handler, call this before proceeding to signing:
If you use a different auth provider, the principle is the same: resolve the caller’s identity, fetch their linked wallets, and reject the request if the destination address is not among them.

3. Generate an idempotency key

Generate a UUID v4 for this payment request. This prevents duplicate transfers if the user retries.

4. Record the signature timestamp

Record the current time as the signature timestamp. Swype enforces a maximum signature age of 15 minutes server-side, so you do not need to manage expiration TTLs yourself.

5. Build the payload JSON

Copy the destination shape you received: the singular triple for a single-destination request, or destinations for a menu. Never sign both.

6. Base64url-encode the payload

Convert the JSON string to a base64url-encoded string. Base64url uses - instead of +, _ instead of /, and no padding.

6. Sign the payload

Sign the encoded payload string (not the raw JSON) using ECDSA P-256 with SHA-256, then base64url-encode the signature:
The input to the signing function is the base64url-encoded payload string (ASCII bytes). The output is the raw DER-encoded ECDSA signature, which is then base64url-encoded.

8. Return the response

Response format

Example response:

Multiple destinations

A user is usually credited at one place: one chain, one token, one wallet address. Some apps can credit the same user at several — USDT on Tron, USDT on Ethereum and USDC on Polygon, say, each at its own address. Pass those as destinations on the deposit request and sign them through as a menu:
What Blink does with a menu. Every deposit settles on exactly one entry, chosen from the asset the user sends:
  1. A source that matches an entry’s chain and token settles there directly — no bridge, no swap, no fee.
  2. Otherwise the same asset on another chain is preferred, then a different token on the same chain, then anything else.
  3. An Ethereum entry is chosen last unless it is a direct match, because destination gas comes out of the delivered amount.
  4. Ties break on your order, so list the destinations you would rather receive first.
Tron entries are honoured on the manual (send-to-address) rail only; Blink one-tap never settles on Tron. Rules. 1–10 entries. No two entries may share a chain and token. Each address must have the shape of its chain (0x hex on EVM, Base58 on Solana and Tron). Send destinations instead of chainId/address/token — a payload carrying both, where the triple is not one of the entries, is rejected. Your signer’s job does not change, except that the ownership check in step 2 must cover every address in the array.

Complete Node.js implementation

A copy-paste-ready Express.js signer endpoint. Replace YOUR_MERCHANT_ID and load your private key from a secure source.

Python implementation

For merchants using Python:

Signing algorithm summary

For merchants implementing the signer in any language:
  1. Build the payload as a JSON string with fields: amount, chainId, address, token, idempotencyKey, callbackScheme, signatureTimestamp, version.
  2. Base64url-encode the JSON string (UTF-8 bytes to base64url, no padding).
  3. Sign the base64url-encoded string (not the raw JSON) using ECDSA with P-256 (prime256v1/secp256r1) and SHA-256. The input to the sign function is the ASCII bytes of the base64url string.
  4. Base64url-encode the raw signature bytes (DER format).
  5. Return the encoded payload, encoded signature, your merchant ID, and a preview object.

Security requirements

The signer endpoint is a security-critical component. A compromised signer allows unauthorized payment link creation.
  • HTTPS only. Never serve the signer over plain HTTP in production.
  • Authenticate callers. The signer should only accept requests from your own frontend. Use session cookies, auth tokens, or CORS restrictions.
  • Verify destination ownership. Confirm the authenticated user controls the destination address before signing. Without this, a malicious user could redirect funds to a wallet they don’t own. See step 2 above.
  • Server-enforced expiry. Swype enforces a maximum signature age of 15 minutes. Include signatureTimestamp in every payload so Swype can reject stale signatures. You do not need to manage expiration TTLs yourself.
  • Rate limit. Protect against abuse by rate-limiting signer requests per user/session.
  • Log, but never log the private key. Log request metadata for debugging, but never log the private key or raw signature in production.
  • CORS. Configure CORS to only allow your frontend origin(s).