Decentralized Asset Transfer
The 'payto' URI scheme builder supports payments as outlined in RFC 8905 and subsequent amendments.
If you're reading this, you've likely already initialized your project. Congratulations!
# To create a new project in the current directory
npm create svelte@latest
# To create a new project in the 'my-app' directory
npm create svelte@latest my-appAfter initializing your project and installing the necessary dependencies with npm install (or pnpm install or yarn), you can start a development server:
npm run dev
# To start the server and open the app in a new browser tab
npm run dev -- --openTo create a production version of your app:
npm run buildYou can preview the production build using:
npm run previewFor deploying your app, you may need to install an adapter suited to your target environment.
You can see the Pro plans for more information.
To customize the PayPass, you can become the authority of your own PayPass.
Issuing authorities1 deliver an object like this example to the email: sales@payto.money
-
id(required): The unique authority ID of the Pass. Must be a valid ID in lowercase, no spaces.- Example:
"pingchb2"
- Example:
-
oric(optional/required for CryptoCard top-up): The ORIC/BIC code of the organization. If set, it is used to verify the organization name. It will be marked as verified if the organization name matches the receiving address.- Example:
"PINGCHB2" - Default: none
- Example:
-
name(required): The organization name displayed on the Pass. If set, it overrides the user's custom organization name.- Example:
"PayTo","My Company Inc."
- Example:
-
url(optional): Your organization's website URL. Displayed in the Pass details.- Example:
"https://payto.money" - Default: none
- Example:
-
icons(optional): Icon URLs for the Pass organized by platform. All formats must be PNG. Accepts standard URLs or IPFS links.apple(object): Apple Wallet iconsicon: 29x29 pxicon2x: 58x58 pxicon3x: 87x87 pxlogo: 160x50 pxlogo2x: 320x100 pxlogo3x: 480x150 px
google(object): Google Wallet iconslogo: 160x50 pxicon: 29x29 pxhero: Hero image
- Unified: Same icons are used for both platforms when available, with platform-specific fallbacks
- Default: PayTo logo with automatic dev/prod URL resolution
-
theme(optional): Color theme for the Pass in hexadecimal format.colorB: Background color (e.g.,"#77bc65")colorF: Foreground color (e.g.,"#192a14")colorTxt: Text/label color (e.g.,"#192a14")- Default: PayTo theme (
colorB: "#2A3950",colorF: "#9AB1D6")
-
forceTheme(optional): Iftrue, forces your theme even if users or system set their own custom colors.- Example:
trueorfalse - Default:
false
- Example:
-
data(optional): Organization data.google: Google Wallet datalocale: The locale used for Google Wallet data (e.g.,"en-GB","de","sk")- Default: parsed or provided in the request
redemptionIssuers: Redemption issuers for Google Wallet (array of strings)- Example:
["1234567890"] - Default: none
- Example:
enableSmartTap: Enable Smart Tap (NFC) for Google Wallet- Example:
trueorfalse - Default:
true
- Example:
merchantLocation: Merchant location for Google Wallet (array of objects, max 10)latitude: Latitude (number)longitude: Longitude (number)- Default: none
- Example:
[ { "latitude": 40.7128, "longitude": -74.0060 } ]
apple: Apple Wallet datalocale: The locale used for Apple Wallet data (e.g.,"en-GB","de","sk")- Default: parsed or provided in the request (overrides the auto-detected locale only for Apple passes)
merchantLocation: Merchant location for Apple Wallet (array of objects, max 10)latitude: Latitude (number)longitude: Longitude (number)relevantText: The text displayed when the location is in range (optional)- Default: none
- Example:
[ { "latitude": 40.7128, "longitude": -74.0060, "relevantText": "You're near my store" } ]
beacons: Custom iBeacon proximity triggers to PayPasses (array of objects, max 10)proximityUUID: The UUID of the iBeacon (required)relevantText: The text displayed when the iBeacon is in range (optional)name: Friendly name for the beacon (optional, stored in the pass for reference)major/minor: Optional numbers if you need to scope to specific beacon instances- Default: none (when provided, these beacons are embedded into the
.pkpass)
- Default: none
-
customCurrency(optional): Custom currency definitions for non-standard currencies or tokens.- Format:
{ "CURRENCY_CODE": { "symbol", "narrowSymbol", "code", "name", "defaultDecimals" } } - Example: CoreCoin (XCB) with ₡ symbol and 2 decimal places
- Default: none
- Library used: exchange-rounding
- Format:
-
currencyLocale(optional): The locale used for currency formatting (e.g.,"en-US","de-DE","sk-SK").- Default: undefined (auto-detected from user's browser)
-
postForm(optional): Iftrue, allows Pass generation via HTML form submission from any origin.- Example:
trueorfalse - Default:
false - Note: When enabled, origin checks are bypassed for form submissions
- Example:
-
api(optional): API access configuration for programmatic Pass generation.allowed: Set totrueto enable API accesssecret: Your API secret key encoded with base64 used to generate bearer tokens (HMAC-SHA256)- Default:
{ "allowed": false, "secret": "" } - Note: When API is enabled, requests must include
Authorization: Bearer <token>header - Token format: HMAC-SHA256 hash of payload + expiration timestamp (default: 1 minute, configurable via
PRIVATE_API_TOKEN_TIMEOUTenv var)
When api.allowed is true, you can generate Passes programmatically by:
- Creating a bearer token using HMAC-SHA256 with your
api.secret - Including the token in the
Authorizationheader:Bearer <token> - Token expires after the configured timeout (default: 1 minute, set via
PRIVATE_API_TOKEN_TIMEOUTenvironment variable)
Both postForm and api.allowed can be enabled simultaneously. The authorization flow checks:
- If API is allowed and valid bearer token is provided → authorized
- If postForm is allowed → authorized
- Otherwise → check origin (must match application origin)
Both postForm and API accept the same payload structure for generating Passes.
- Method:
POST - Endpoint:
https://payto.money/pass?authority={id} - Content-Type:
application/json(for API) orapplication/x-www-form-urlencoded(for forms) - Headers (for API):
Authorization: Bearer <token>
Required Fields:
-
hostname(string): Payment type identifier- Examples:
"ican","ach","iban","bic","upi","pix","void"
- Examples:
-
props(object): Network and payment parametersnetwork(string): Network identifier (e.g.,"btc","eth","core","geo","plus", etc.)destination(string): Payment destination addressparams(object): Payment parametersamount(object): Payment amountvalue(number): Amount value
message(object, optional): Payment messagevalue(string): Message text
rc(object, optional): Recurring paymentvalue(string): Recurrence period ("daily","weekly","monthly","yearly")
dl(object, optional): Deadline/expirationvalue(number): Unix timestamp or minutes (1-60)
donate(object, optional): Donation flagvalue(number): 0 or 1
loc(object, optional): Location (forvoidpayments withgeoorplusnetwork)value(string): Coordinates or Plus Code
split(object, optional): Split paymentvalue(number): Split amount or percentageisPercent(boolean): Whether split is percentageaddress(string): Split destination address
design(object, optional): Custom design for this paymentitem(string): Item description
Optional Fields:
-
design(object): Visual customizationorg(string): Organization name (overrides authority name if not forced)colorF(string): Foreground color (hex, e.g.,"#9AB1D6")colorB(string): Background color (hex, e.g.,"#2A3950")barcode(string): Barcode type ("qr","pdf417","aztec","code128")rtl(boolean): Right-to-left layoutlang(string): Language code (e.g.,"en","de","sk","zh-CN","ko-KR")item(string): Item description
-
destination(string, form data only): Payment destination address- When provided as a form field (not in JSON API), this overrides
props.destination - Useful for dynamic destination addresses in HTML forms
- Only works with
application/x-www-form-urlencodedrequests, not JSON API requests
- When provided as a form field (not in JSON API), this overrides
-
membership(string, optional): Member address for tracking -
authority(string, optional): Authority ID (auto-populated from URL parameter) -
os(string, optional): Target operating system ("ios","android", or"unknown")- Auto-detected from user agent if not specified
- Determines which wallet integration to use
<!-- Form submission from external website -->
<form method="POST" action="https://payto.money/pass?authority=your_id">
<input type="hidden" name="hostname" value="ican" />
<input type="hidden" name="props" value='{
"network": "xcb",
"destination": "cb7147879011ea207df5b35a24ca6f0859dcfb145999",
"params": {
"amount": { "value": "10.50" },
"message": { "value": "Invoice #INV-2024-001" },
"id": { "value": "INV-2024-001" },
"rc": { "value": "monthly" }
}
}' />
<input type="hidden" name="design" value='{
"colorF": "#10B981",
"colorB": "#065F46",
"barcode": "qr",
"lang": "en",
"org": "My Company Inc.",
"item": "Premium Subscription"
}' />
<button type="submit">Download PayPass</button>
</form><!-- Form submission from external website -->
<form method="POST" action="https://payto.money/pass?authority=your_id">
<input type="hidden" name="hostname" value="ican" />
<input type="hidden" name="props" value='{
"network": "xcb",
"destination": "cb7147879011ea207df5b35a24ca6f0859dcfb145999",
"params": {
"amount": { "value": "10.50" },
"message": { "value": "Invoice #INV-2024-001" },
"id": { "value": "INV-2024-001" },
"rc": { "value": "monthly" }
}
}' />
<input type="hidden" name="design" value='{
"colorF": "#10B981",
"colorB": "#065F46",
"barcode": "qr",
"lang": "en",
"org": "My Company Inc.",
"item": "Premium Subscription"
}' />
<button type="submit" name="os" value="android" formtarget="_blank">Add to Google Wallet</button>
<button type="submit" name="os" value="ios">Add to Apple Wallet</button>
</form>curl -X POST https://payto.money/pass?authority=your_id \
-H "Authorization: Bearer YOUR_TOKEN_HERE" \
-H "Content-Type: application/json" \
-d '{
"hostname": "ican",
"props": {
"network": "xcb",
"destination": "cb7147879011ea207df5b35a24ca6f0859dcfb145999",
"params": {
"amount": { "value": "100.50" },
"message": { "value": "Invoice #INV-2024-001" },
"id": { "value": "INV-2024-001" },
"rc": { "value": "monthly" }
}
},
"design": {
"colorF": "#10B981",
"colorB": "#065F46",
"barcode": "qr",
"lang": "en",
"org": "My Company Inc.",
"item": "Premium Subscription"
}
}'<!-- Using destination field to override props.destination -->
<form method="POST" action="https://payto.money/pass?authority=your_id" enctype="application/x-www-form-urlencoded">
<input type="hidden" name="hostname" value="ican" />
<!-- User can input their own address -->
<label for="userAddress">Your Wallet Address:</label>
<input type="text" id="userAddress" name="destination" value="cb7147879011ea207df5b35a24ca6f0859dcfb145999" required />
<!-- Props contains default/fallback destination -->
<input type="hidden" name="props" value='{
"network": "xcb",
"destination": "default-address-will-be-overridden",
"params": {
"amount": { "value": "50" },
"message": { "value": "Donation" }
}
}' />
<input type="hidden" name="design" value='{
"colorF": "#10B981",
"colorB": "#065F46",
"barcode": "qr",
"lang": "en"
}' />
<button type="submit">Generate Pass for My Address</button>
</form>Note: The form field destination will override props.destination, allowing users to input their own wallet address dynamically.
{
"hostname": "ican",
"props": {
"network": "xcb",
"destination": "cb71...",
"params": {
"amount": {
"value": 100
},
"message": {
"value": "Monthly subscription"
},
"rc": {
"value": "monthly"
},
"dl": {
"value": 30
},
"donate": {
"value": 0
}
},
"split": {
"value": 10,
"isPercent": true,
"address": "cb72..."
},
"design": {
"item": "Premium Plan"
}
},
"design": {
"org": "My Company",
"colorF": "#192a14",
"colorB": "#77bc65",
"barcode": "qr",
"rtl": false,
"lang": "en",
"item": "Premium Subscription"
},
"membership": "cb73..."
}Response:
- iOS/Apple Wallet:
.pkpassfile download (binary) - Android/Google Wallet: JSON response with
saveUrlfor Google Wallet integration - Error: JSON with error message and HTTP status code
The PayPass generation system automatically detects the user's operating system and provides appropriate wallet integration:
- iOS Detection: Shows "Add PayPass to Apple Wallet" button, generates
.pkpassfile - Android Detection: Shows "Add PayPass to Google Wallet" button, generates Google Wallet save link
- Unknown/Desktop: Shows both wallet options for maximum compatibility
The system uses navigator.userAgent to detect iOS (iphone|ipad|ipod) and Android (android) devices, providing a seamless user experience across all platforms.
The PayPass generation interface includes:
- Responsive Design: Buttons adapt to screen size (half-width on desktop, full-width stacked on mobile)
- OS-Aware Interface: Shows only relevant wallet button when OS is detected, both buttons when unknown
- Consistent Styling: Unified button design with proper icon sizing and spacing
- Accessibility: Proper focus states, ARIA labels, and keyboard navigation support
- Visual Feedback: Loading states, disabled states, and hover effects for better UX
After successfully registering the authority, to issue PayPass directly from payto UI you need to append the authority parameter to the URL:
https://payto.money?authority={id}- TypeScript/JavaScript: payto-rl
- Flutter: flutter_paytorl
This project is licensed under the CORE License.
If you find this project useful, please consider supporting it:
{ "id": "pingchb2", // ID in lowercase, no spaces "oric": "PINGCHB2", // ORIC/BIC code of the organization (optional, required for CryptoCard top-up) "name": "Ping Exchange", // Name of the organization "url": "https://ping.exchange", // Website of the organization "icons": { // Organization icons "apple": { // Apple Wallet icons "icon": "https://…/icons/apple/icon.png", // Icon 29x29 px "icon2x": "https://…/icons/apple/icon@2x.png", // Icon 58x58 px "icon3x": "https://…/icons/apple/icon@3x.png", // Icon 87x87 px "logo": "https://…/icons/apple/logo.png", // Logo 160x50 px "logo2x": "https://…/icons/apple/logo@2x.png", // Logo 320x100 px "logo3x": "https://…/icons/apple/logo@3x.png" // Logo 480x150 px }, "google": { // Google Wallet icons "logo": "https://…/icons/google/logo.png", // Logo 160x50 px "icon": "https://…/icons/google/icon.png", // Icon 29x29 px "hero": "https://…/icons/google/hero.png", // Hero image } }, "theme": { // Organization theme "colorB": "#77bc65", // Background color "colorF": "#192a14", // Foreground color "colorTxt": "#192a14" // Text/label color }, "forceTheme": false, // If true, forces your theme even if users or system set their own custom colors "data": { // Organization data "google": { // Google Wallet data "locale": "en", // The locale used for Google Wallet data (e.g., `"en-GB"`, `"de"`, `"sk"`) "redemptionIssuers": [ // Redemption issuers for Google Wallet "1234567890" // Redemption issuer ID ], "enableSmartTap": true, // Enable Smart Tap (NFC) for Google Wallet "merchantLocation": [ { "latitude": 40.7128, "longitude": -74.0060 } ] }, "apple": { // Apple Wallet data "locale": "en", // The locale used for Apple Wallet data (e.g., `"en-GB"`, `"de"`, `"sk"`) "merchantLocation": [ { "latitude": 40.7128, "longitude": -74.0060, "relevantText": "You're near my store" } ], "beacons": [ // Custom iBeacon proximity triggers to PayPasses { "proximityUUID": "F8F589E9-C07E-58B0-AEAB-A36BE4D48FAC", // The UUID of the iBeacon "relevantText": "You're near my store", // The text displayed when the iBeacon is in range "name": "My Store" // The name of the iBeacon } ] } }, "customCurrency": { // Custom currency definitions for non-standard currencies or tokens "XCB": { "symbol": "₡", "narrowSymbol": "₡", "code": "XCB", "name": "CoreCoin", "defaultDecimals": 2 } }, "currencyLocale": "en-US", // The locale used for currency formatting (e.g., `"en-US"`, `"de-DE"`, `"sk-SK"`) "postForm": false, // If true, allows Pass generation via HTML form submission from any origin "api": { // API access configuration for programmatic Pass generation "allowed": false, // Set to `true` to enable API access "secret": "your-api-secret-here" // Your API secret key encoded with base64 used to generate bearer tokens (HMAC-SHA256) } }