Introduction
What is DogeConnect?
DogeConnect is a payment protocol for transmitting a detail-rich Payment Request from a Vendor to a Client Wallet and receiving a Payment Submission containing a signed transaction, which is validated and relayed to the network by a Payment Relay.
Note: this specification is ready to implement; please provide implementation feedback.
DogeConnect comprises:
- a Payment QR Code specification,
- a Payment Envelope JSON schema,
- an API specification for an internet Payment Relay.
Through DogeConnect, Vendors can issue detailed payment requests to customers with itemised goods and services as well as taxes and fees, which the Client Wallet can display to the user along with the vendor’s name and logo.
In the DogeConnect model, the vendor or their nominated Payment Relay validates the transaction and determines their desired risk appetite, the number of blockchain confirmations they require, etc.
sequenceDiagram participant w as Wallet participant c as Client participant v as Vendor participant r as Relay c ->> v: Let me buy something v ->> r: Start payment (itemised payment info) r ->> v: QR Code URI, payment ID v ->> c: QR Code URI c ->> w: Scans QR Code / NFC w ->> r: Fetch Payment Envelope r ->> w: Payment Envelope w ->> w: Verify Hash (MITM) w ->> w: Display itemised payment w ->> w: Confirm/sign transaction w ->> r: Submit signed transaction r ->> r: Validate transaction r ->> r: Submit to dogecoin network r -->> v: Payment accepted r ->> r: Wait for confirmation r -->> v: Payment confirmed
Payment QR-Code
Payment QR-Codes
The de-facto standard Dogecoin QR-Code encodes a URI like this:
dogecoin:DQ6dt7wCjLDxtdSwCYSAMFHwrD5Q1xybmL?amount=8.25
The URI scheme dogecoin: is associated with wallet software on a customer’s device,
so the wallet will open when the customer scans a QR Code with the device’s camera.
The above QR-Code requests a payment of 8.25 dogecoin to a specified address. Traditional wallets simply pre-fill the amount and allow the user to broadcast their payment transaction directly to the dogecoin network.
This payment method has some problems:
- The vendor doesn’t know the customer has initiated payment until the transaction is mined into a block, one or more minutes later.
- The customer can choose the amount to pay, or can pay with multiple transactions, which is difficult for the vendor to detect and reconcile.
- The customer doesn’t see what they’re paying for or who the vendor is, before signing the transaction.
DogeConnect QR-Codes
Due to the limited space available in a QR Code, DogeConnect addresses these problems by including the URL of an itemised Payment Request.
DogeConnect adds two parameters to the QR-Code:
dogecoin:DQ6dt7wCjLDxtdSwCYSAMFHwrD5Q1xybmL?amount=8.25&dc=example.com%2Fdc%2Fxyz123&h=p212MS4KXZBX5uDNXWmB
- dc is a
https://URL the wallet can use to fetch the Payment Envelope - h is a hash of the Payment Relay public key, as a security check
The dc URL always has the https:// prefix trimmed off to save space in the
QR code. This URL should be kept as short as possible.
Wallets that support DogeConnect should fetch the Payment Envelope from the dc URL
rather than using the basic dogecoin address and amount; those fields provide backwards
compatibility with older wallets.
Please note that URI parameters are percent-encoded in line with RFC 3986;
wallets MUST decode the parameters according to section 2.1. We strongly recommend
using a URL/URI decoding library, which will handle all decoding requirements.
(Otherwise, make sure to use something like decodeURIComponent or QueryUnescape
functions on each parameter, after splitting up the URI string.)
Wallets MUST use the h hash to verify that the Payment Envelope came from the
vendor’s nominated Payment Relay, as this protects against man-in-the-middle (MITM)
attacks and redirection of funds.
Wallet Implementation
- Receive the QR Code URI from the host system
- Use a URL/URI decoding library to break apart the URI into a scheme (
dogecoin:), a path component (the dogecoin address) and a series of query parameters: theamount,dcandhparameters. - Check if both
dcandhare present, non-empty parameters; this indicates a DogeConnect payment. Both parameters must be present for a secure interaction (for example, a missinghparameter suggests a MITM attack.) If either are missing or empty, fall back on the dogecoin address and amount parameters, i.e. treat it as a non-DogeConnect QR Code. - Decode the Base64-encoded
hparameter, using a library or Base64 decode routine. If this fails to decode, or doesn’t yield 15 bytes of data, the request is invalid (this also suggests a MITM attack.) - Concatenate
https://onto the start of thedcparameter. It is always a https URL, but thehttps://is trimmed off to keep the QR Codes from growing too large. - Fetch the DogeConnect Payment Envelope JSON payload from the URL created in step 4. If this request fails, make a few attempts before giving up, as sometimes there are spurious network errors (especially on mobile devices.)
- Decode the JSON Payment Envelope. If decoding fails, consider making another attempt (as part of the above retry process) as the response may be a spurious error from a network gateway; retries make the whole process more reliable.
- Take the
pubkeyfield of the Payment Envelope, which is hex-encoded. Use a hex decode routine to decode to raw bytes, verify that you decoded 32 bytes, then use SHA-256 to hash those bytes. - Compare the first 15 bytes of the hash from step 8 with the 15 bytes of the
decoded
hparameter from step 4. If they differ, the request is invalid and has likely been tampered with (this strongly suggests a MITM attack.) - Proceed with Payment Envelope decoding and signature verification described in the next section.
The goals of the above process are to recognise a DogeConnect QR Code, fetch the
Payment Envelope from the vendor’s nominated Payment Relay, and verify that
the Payment Envelope actually came from the nominated Payment Relay, by
verifying the pubkey hash included in the QR Code.
This protects against man-in-the-middle (MITM) attacks and redirection of funds to a potential attacker.
Payment Envelope
Payment Envelope Schema
Connect Envelope
{
"version": "1.0", // MUST be 1.0
"payload": "YTc2ZDc2MzEyZ..", // Base64-encoded JSON payload (e.g. Connect Payment)
"pubkey": "c8a6927d0a004..", // Relay Public Key, BIP-340 Schnorr X-only (32 bytes)
"sig": "202d831c6437c.." // Payload Signature, BIP-340 Schnorr (64 bytes)
}
Connect Payment
{
"type": "payment", // MUST be "payment"
"id": "PID-123", // Relay-unique Payment ID
"issued": "2006-01-02T15:04:05-07:00", // RFC 3339 Timestamp
"timeout": 60, // Timeout in seconds, do not pay after this time
"relay": "https://relay.example.com/..", // Payment Relay to submit payment tx
"relay_token": "eyJpZCI6IlBJRC...", // Opaque relay-generated token (optional)
"fee_per_kb": "0.01001386", // Min fee per 1000 bytes the relay will accept, 8-DP string
"max_size": 10000, // Max tx size in bytes the relay will accept
"vendor_icon": "https://example.com/..", // Vendor icon URL, JPG or PNG (optional)
"vendor_name": "Vendor Co", // Vendor display name
"vendor_address": "123 Example St", // Vendor business address (optional)
"vendor_url": "https://example.com", // Vendor website URL (optional)
"vendor_order_url": "https://example.com/..", // URL to view order on vendor's site (optional)
"vendor_order_id": "INV-2025-0042", // Vendor's unique order identifier (optional)
"order_reference": "A073", // Short customer-facing order identifier (optional)
"note": "Thank you for your order!", // Free-text note from vendor to customer (optional)
"total": "41.9395", // Total including fees and taxes, 8-DP string
"fees": "1.0", // Fees subtotal, 8-DP string (optional)
"taxes": "1.9495", // Taxes subtotal, 8-DP string (optional)
"fiat_total": "5.00", // Total in fiat currency, decimal string (optional)
"fiat_tax": "0.23", // Taxes in fiat currency, decimal string (optional)
"fiat_currency": "USD", // ISO 4217 currency code (required with fiat_total/fiat_tax)
"items": [], // List of line items to display (Connect Items)
"outputs": [] // List of outputs to pay (Connect Outputs)
}
Connect Item
{
"type": "item", // item, tax, fee, shipping, discount, donation
"id": "SK-101", // unique item ID or SKU
"icon": "https://example.com/itm/ic.png", // icon URL, JPG or PNG (optional)
"name": "Doge Plushie", // name to display
"desc": "One doge plushie in a soft bag", // item description to display (optional)
"count": 1, // number of units >= 1
"unit": "38.99", // unit price, 8-DP string
"total": "38.99", // count x unit, 8-DP string
"tax": "1.9495" // tax on this item, 8-DP string (optional)
}
Connect Output
{
"address": "DQ6dt7wCjLDxtdSwCYSAMFHwrD5Q1xybmL", // Dogecoin Address
"amount": "1.0" // Amount, 8-DP string
}
8-DP string
Fields marked 8-DP string are DECIMAL numbers with up to 8 decimal places,
which represent a Koinu value – the smallest unit of Dogecoin.
They are strings to preserve accuracy; most JSON parsers treat numbers as floating point, which sacrifices some accuracy (e.g. a value of “0.1” cannot be stored accurately as a floating point number.)
These can be parsed using a Decimal library, or code like this.
Payment Envelope Verification
The DogeConnect Payment Envelope contains a Base64-encoded JSON payload, the Payment Relay’s public key, and a digital signature of the payload signed by the Payment Relay’s private key.
Use the following steps to decode and verify the payload.
- Ensure you verify the
pubkeyhash as described in the Payment QR-Code section. - Decode the Base64-encoded
payloadfield, yielding bytes. - Decode the Hex-encoded
pubkeyandsigfields, yielding bytes. - Compute the Double-SHA256 of the decoded payload bytes from step 2, i.e.
SHA-256(SHA-256(bytes)) - Apply BIP-340
lift_xalgorithm on thepubkeybytes to recover the full Public Key. A BIP-340 library will supply this step (it’s essentially parsing a compressed public key.) - Verify the BIP-340 Schnorr signature, using the Double-SHA256 hash as the
message, and the full Public Key andsig. A BIP-340 library will supply the signature verification algorithm. If this step fails, it suggests a MITM attack or faulty implementation. - Parse the JSON payload bytes using a standard JSON parser.
- Check the
timeoutfield: do not submit a payment transaction after the timeissued+timeout. - Display the payment information and ask the user to confirm payment.
- Create and sign a payment transaction and submit it to the Payment Relay.
If the Connect Payment contains a
relay_token, include it in the Payment Submission.
The goals of the above process are to verify that the Payment Envelope is cryptographically signed by the Payment Relay’s private key, i.e. the envelope was created by the Payment Relay.
A reference implementation of these algorithms exist at github.com/dogeorg/dogeconnect-go which can be packaged for mobile using gomobile bind.
BIP-340 Implementations
Payment Relay
The Payment Relay base URL is found in the relay field of Connect Payment.
The following Web API must be implemented by a Payment Relay:
| URL | Method | Post Data | Response | Function |
|---|---|---|---|---|
| relay/pay | POST | Payment Submission | Payment Status | Submit payment tx |
| relay/status | POST | Status Query | Payment Status | Query payment status |
Requests and responses are JSON payloads; JSON must be UTF-8 encoded.
All responses must include a Cache-Control: no-store header, to avoid
leaking payment information into caches. Both endpoints additionally use
the POST method to avoid caching edge-cases (e.g. error responses.)
The Relay must implement the pay endpoint with idempotence in
the following way: if the status of payment id is already accepted or
confirmed, it responds to another pay request with that status, ignoring
the supplied tx. This is because wallets will retry their pay request
if they did not receive or process the first reply.
Relay Token
A Relay may include an opaque relay_token in the Connect Payment. If present,
the wallet must echo it back verbatim in the Payment Submission. The token is
opaque to the wallet — it should not attempt to parse or interpret its contents.
The relay token enables stateless validation: the relay can embed whatever parameters it needs inside the token, avoiding additional lookups at submission time.
The token format is entirely up to the relay implementation.
Payment Submission
The wallet’s submission to the relay’s pay endpoint in response to a Connect Payment.
{
"id": "PID-123", // Relay-unique Payment ID from Connect Payment
"tx": "489c47f8a3ba3293737..", // Hex-encoded signed dogecoin transaction
"refund": "DKY8dUTQthSX..", // Dogecoin address for refunds (recommended)
"relay_token": "eyJpZCI6IlBJRC..." // Relay token from Connect Payment, if present
}
Payment Status
Both the pay and status endpoints return a Payment Status response.
200 — Accepted:
{
"id": "PID-123",
"status": "accepted",
"txid": "a1b2c3d4e5..",
"required": 5,
"confirmed": 0,
"due_sec": 300
}
403 — Declined:
{
"id": "PID-123",
"status": "declined",
"reason": "Transaction deemed too risky"
}
The status field is a PaymentStatus enum value. The txid field
contains the hex-encoded transaction ID and is present whenever the
status is accepted or confirmed. The confirmed_at field is an RFC 3339 timestamp indicating when the
submitted transaction reached the required number of block confirmations;
it is only present when the status is confirmed.
The status will be accepted if the Relay requires one or more block
confirmations on the blockchain, reflected in the required field.
The status may be confirmed if the Relay deems the payment low-risk.
The required, confirmed and due_sec fields are present whenever the
status is accepted or confirmed. The required field indicates how many
block confirmations the Relay requires; confirmed is the current count
on-chain; due_sec is the estimated seconds remaining until confirmed.
When the payment status is confirmed, the confirmed field is always
greater or equal to required, and the due_sec field is always zero.
Payments transition from unpaid to accepted after the signed transaction
is submitted to the pay endpoint (provided payment was accepted.) After
the required number of block confirmations have been seen on-chain, the
payment status transitions to confirmed.
Note: there are some edge-cases where the confirmed count can reduce,
i.e. during a short-term blockchain fork.
Error Response
{
"error": "invalid_tx",
"message": "Transaction outputs do not match requested amounts"
}
HTTP Status Codes
pay endpoint
| HTTP | Body | Meaning |
|---|---|---|
| 200 | Payment Status | Accepted or confirmed |
| 400 | Error Response | Programming error (malformed tx, wrong outputs, expired, invalid token) |
| 403 | Payment Status | Declined (status: "declined") |
| 404 | Error Response | Unknown payment ID |
| 500 / 503 | - | Transient server error; retry with backoff |
status endpoint
| HTTP | Body | Meaning |
|---|---|---|
| 200 | Payment Status | Status returned |
| 404 | Error Response | Unknown payment ID |
| 500 / 503 | - | Transient server error; retry with backoff |
A 400 or 403 response indicates a permanent failure; wallets should not retry. A 500 or 503 response is transient — the wallet should assume the relay will recover and retry a few times with exponential backoff and a small random jitter.
Status Query
This allows the wallet to query the current status of a payment.
{
"id": "PID-123" // Relay-unique Payment ID from Connect Payment
}
Schema Reference
Quick reference for all DogeConnect JSON schemas.
All 8-DP string fields are decimal strings with up to 8 decimal places representing Dogecoin amounts.
See Payment Envelope for details.
Optional String Fields
- Payment Relays SHOULD include optional string fields as empty strings
""when no value is available, rather than omitting them. - Wallets MUST treat a missing field and an empty string
""identically (i.e. as not provided). - Wallets SHOULD provide sensible placeholders for missing display fields (e.g. icons).
Enums
EnvelopeType
| Value | Description |
|---|---|
payment | Connect Payment envelope |
ItemType
| Value | Description |
|---|---|
item | Purchasable item |
tax | Tax line item |
fee | Fee line item |
shipping | Shipping charge |
discount | Discount (amount MUST be negative) |
donation | Donation |
PaymentStatus
| Value | pay | status | Description |
|---|---|---|---|
unpaid | — | yes | No transaction submitted yet |
accepted | yes | yes | Transaction received, awaiting confirmations |
confirmed | yes | yes | Required confirmations reached |
declined | yes | — | Relay/vendor rejected the payment |
ErrorCode
| Value | Description |
|---|---|
not_found | Unknown payment ID |
expired | Payment timeout has elapsed |
invalid_tx | Transaction is malformed or cannot be decoded |
invalid_outputs | Transaction does not pay the correct amounts/addresses |
invalid_token | Relay token is missing, corrupt, or failed verification |
Connect Envelope
Signed wrapper around a Connect Payment payload.
{
"version": "1.0", // MUST be 1.0
"payload": "YTc2ZDc2MzEyZ..", // Base64-encoded JSON payload (e.g. Connect Payment)
"pubkey": "c8a6927d0a004..", // Relay Public Key, BIP-340 Schnorr X-only (32 bytes)
"sig": "202d831c6437c.." // Payload Signature, BIP-340 Schnorr (64 bytes)
}
| Field | Type | Required | Description |
|---|---|---|---|
version | string | yes | Protocol version, MUST be "1.0" |
payload | string | yes | Base64-encoded JSON payload (Connect Payment) |
pubkey | string | yes | Relay public key, BIP-340 Schnorr X-only (32 bytes, hex) |
sig | string | yes | Payload signature, BIP-340 Schnorr (64 bytes, hex) |
Connect Payment
The decoded payload inside a Connect Envelope.
{
"type": "payment", // MUST be "payment"
"id": "PID-123", // Relay-unique Payment ID
"issued": "2006-01-02T15:04:05-07:00", // RFC 3339 Timestamp
"timeout": 60, // Timeout in seconds, do not pay after this time
"relay": "https://relay.example.com/..", // Payment Relay to submit payment tx
"relay_token": "eyJpZCI6IlBJRC...", // Opaque relay-generated token (optional)
"fee_per_kb": "0.01001386", // Min fee per 1000 bytes the relay will accept, 8-DP string
"max_size": 10000, // Max tx size in bytes the relay will accept
"vendor_icon": "https://example.com/..", // Vendor icon URL, JPG or PNG (optional)
"vendor_name": "Vendor Co", // Vendor display name
"vendor_address": "123 Example St", // Vendor business address (optional)
"vendor_url": "https://example.com", // Vendor website URL (optional)
"vendor_order_url": "https://example.com/..", // URL to view order on vendor's site (optional)
"vendor_order_id": "INV-2025-0042", // Vendor's unique order identifier (optional)
"order_reference": "A073", // Short customer-facing order identifier (optional)
"note": "Thank you for your order!", // Free-text note from vendor to customer (optional)
"total": "41.9395", // Total including fees and taxes, 8-DP string
"fees": "1.0", // Fees subtotal, 8-DP string (optional)
"taxes": "1.9495", // Taxes subtotal, 8-DP string (optional)
"fiat_total": "5.00", // Total in fiat currency, decimal string (optional)
"fiat_tax": "0.23", // Taxes in fiat currency, decimal string (optional)
"fiat_currency": "USD", // ISO 4217 currency code (required with fiat_total/fiat_tax)
"items": [], // List of line items to display (Connect Items)
"outputs": [] // List of outputs to pay (Connect Outputs)
}
| Field | Type | Required | Description |
|---|---|---|---|
type | string | yes | EnvelopeType enum; MUST be "payment". Exists for forward compatibility with future envelope types |
id | string | yes | Relay-unique payment ID |
issued | string | yes | RFC 3339 timestamp |
timeout | integer | yes | Timeout in seconds; do not pay after issued + timeout |
relay | string | yes | Payment Relay base URL |
relay_token | string | no | Opaque relay-generated token; wallet MUST echo in Payment Submission if present |
fee_per_kb | string | yes | Minimum fee per 1000 bytes that the Payment Relay is willing to accept. Wallet MUST construct a transaction meeting at least this fee rate, 8-DP string |
max_size | integer | yes | Maximum size of transaction in bytes that the Payment Relay is willing to accept |
vendor_icon | string | no | Vendor icon URL (JPG or PNG); wallet SHOULD use a placeholder when not provided |
vendor_name | string | yes | Vendor display name |
vendor_address | string | no | Vendor business address |
vendor_url | string | no | Vendor website URL |
vendor_order_url | string | no | URL to view this order on vendor’s site |
vendor_order_id | string | no | Vendor’s unique order identifier |
order_reference | string | no | Short customer-facing order identifier |
note | string | no | Free-text note from vendor to customer |
total | string | yes | Total including fees and taxes, 8-DP string |
fees | string | no | Fees subtotal, 8-DP string |
taxes | string | no | Taxes subtotal, 8-DP string |
fiat_total | string | no | Total in fiat currency, decimal string |
fiat_tax | string | no | Taxes in fiat currency, decimal string |
fiat_currency | string | conditional | ISO 4217 currency code; required when fiat_total or fiat_tax is present |
items | array | yes | List of Connect Items |
outputs | array | yes | List of Connect Outputs |
Connect Item
A line item within a Connect Payment.
{
"type": "item", // item, tax, fee, shipping, discount, donation
"id": "SK-101", // unique item ID or SKU
"icon": "https://example.com/itm/ic.png", // icon URL, JPG or PNG (optional)
"name": "Doge Plushie", // name to display
"desc": "One doge plushie in a soft bag", // item description to display (optional)
"count": 1, // number of units >= 1
"unit": "38.99", // unit price, 8-DP string
"total": "38.99", // count x unit, 8-DP string
"tax": "1.9495" // tax on this item, 8-DP string (optional)
}
| Field | Type | Required | Description |
|---|---|---|---|
type | string | yes | ItemType enum |
id | string | yes | Unique item ID or SKU |
icon | string | no | Icon URL (JPG or PNG); wallet SHOULD use a placeholder when not provided |
name | string | yes | Display name |
desc | string | no | Item description |
count | integer | yes | Number of units (>= 1) |
unit | string | yes | Unit price, 8-DP string |
total | string | yes | count x unit, 8-DP string |
tax | string | no | Tax on this item, 8-DP string |
Connect Output
A transaction output the wallet must pay.
{
"address": "DQ6dt7wCjLDxtdSwCYSAMFHwrD5Q1xybmL", // Dogecoin Address
"amount": "1.0" // Amount, 8-DP string
}
| Field | Type | Required | Description |
|---|---|---|---|
address | string | yes | Dogecoin address |
amount | string | yes | Amount to pay, 8-DP string |
Payment Submission
The wallet’s submission to the relay’s pay endpoint in response to a Connect Payment.
{
"id": "PID-123", // Relay-unique Payment ID from Connect Payment
"tx": "489c47f8a3ba3293737..", // Hex-encoded signed dogecoin transaction
"refund": "DKY8dUTQthSX..", // Dogecoin address for refunds (recommended)
"relay_token": "eyJpZCI6IlBJRC..." // Relay token from Connect Payment, if present
}
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | Relay-unique payment ID from Connect Payment |
tx | string | yes | Hex-encoded signed Dogecoin transaction |
refund | string | no | Customer-provided Dogecoin address for refunds; tx input addresses may not return funds to the customer (e.g. exchange deposits), so this provides a guaranteed return path (recommended) |
relay_token | string | conditional | Opaque relay token; required when the Connect Payment contained a relay_token |
Payment Status
The relay’s response from both the pay and status endpoints.
{
"id": "PID-123",
"status": "accepted",
"txid": "a1b2c3d4e5..",
"required": 5,
"confirmed": 0,
"due_sec": 300
}
{
"id": "PID-123",
"status": "declined",
"reason": "Transaction deemed too risky"
}
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | Relay-unique payment ID |
status | string | yes | PaymentStatus enum |
reason | string | conditional | Reason for decline; present when declined |
txid | string | conditional | Hex-encoded tx ID; present when accepted or confirmed |
confirmed_at | string | conditional | RFC 3339 timestamp of when the tx reached the required block confirmations; present when confirmed |
required | integer | conditional | Block confirmations required; present when accepted or confirmed |
confirmed | integer | conditional | Current block confirmations; present when accepted or confirmed |
due_sec | integer | conditional | Estimated seconds until confirmed; present when accepted or confirmed |
Status Query
Query the current status of a payment, submitted to the relay’s status endpoint.
{
"id": "PID-123" // Relay-unique Payment ID from Connect Payment
}
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | Relay-unique payment ID from Connect Payment |
Error Response
Returned by the relay when a request fails.
{
"error": "invalid_tx",
"message": "Transaction outputs do not match requested amounts"
}
| Field | Type | Required | Description |
|---|---|---|---|
error | string | yes | ErrorCode enum |
message | string | yes | Human-readable error detail |