Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

  1. Receive the QR Code URI from the host system
  2. 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: the amount, dc and h parameters.
  3. Check if both dc and h are present, non-empty parameters; this indicates a DogeConnect payment. Both parameters must be present for a secure interaction (for example, a missing h parameter 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.
  4. Decode the Base64-encoded h parameter, 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.)
  5. Concatenate https:// onto the start of the dc parameter. It is always a https URL, but the https:// is trimmed off to keep the QR Codes from growing too large.
  6. 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.)
  7. 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.
  8. Take the pubkey field 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.
  9. Compare the first 15 bytes of the hash from step 8 with the 15 bytes of the decoded h parameter from step 4. If they differ, the request is invalid and has likely been tampered with (this strongly suggests a MITM attack.)
  10. 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.

  1. Ensure you verify the pubkey hash as described in the Payment QR-Code section.
  2. Decode the Base64-encoded payload field, yielding bytes.
  3. Decode the Hex-encoded pubkey and sig fields, yielding bytes.
  4. Compute the Double-SHA256 of the decoded payload bytes from step 2, i.e. SHA-256(SHA-256(bytes))
  5. Apply BIP-340 lift_x algorithm on the pubkey bytes to recover the full Public Key. A BIP-340 library will supply this step (it’s essentially parsing a compressed public key.)
  6. Verify the BIP-340 Schnorr signature, using the Double-SHA256 hash as the message, and the full Public Key and sig. A BIP-340 library will supply the signature verification algorithm. If this step fails, it suggests a MITM attack or faulty implementation.
  7. Parse the JSON payload bytes using a standard JSON parser.
  8. Check the timeout field: do not submit a payment transaction after the time issued + timeout.
  9. Display the payment information and ask the user to confirm payment.
  10. 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:

URLMethodPost DataResponseFunction
relay/payPOSTPayment SubmissionPayment StatusSubmit payment tx
relay/statusPOSTStatus QueryPayment StatusQuery 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

HTTPBodyMeaning
200Payment StatusAccepted or confirmed
400Error ResponseProgramming error (malformed tx, wrong outputs, expired, invalid token)
403Payment StatusDeclined (status: "declined")
404Error ResponseUnknown payment ID
500 / 503-Transient server error; retry with backoff

status endpoint

HTTPBodyMeaning
200Payment StatusStatus returned
404Error ResponseUnknown 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

ValueDescription
paymentConnect Payment envelope

ItemType

ValueDescription
itemPurchasable item
taxTax line item
feeFee line item
shippingShipping charge
discountDiscount (amount MUST be negative)
donationDonation

PaymentStatus

ValuepaystatusDescription
unpaidyesNo transaction submitted yet
acceptedyesyesTransaction received, awaiting confirmations
confirmedyesyesRequired confirmations reached
declinedyesRelay/vendor rejected the payment

ErrorCode

ValueDescription
not_foundUnknown payment ID
expiredPayment timeout has elapsed
invalid_txTransaction is malformed or cannot be decoded
invalid_outputsTransaction does not pay the correct amounts/addresses
invalid_tokenRelay 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)
}
FieldTypeRequiredDescription
versionstringyesProtocol version, MUST be "1.0"
payloadstringyesBase64-encoded JSON payload (Connect Payment)
pubkeystringyesRelay public key, BIP-340 Schnorr X-only (32 bytes, hex)
sigstringyesPayload 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)
}
FieldTypeRequiredDescription
typestringyesEnvelopeType enum; MUST be "payment". Exists for forward compatibility with future envelope types
idstringyesRelay-unique payment ID
issuedstringyesRFC 3339 timestamp
timeoutintegeryesTimeout in seconds; do not pay after issued + timeout
relaystringyesPayment Relay base URL
relay_tokenstringnoOpaque relay-generated token; wallet MUST echo in Payment Submission if present
fee_per_kbstringyesMinimum 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_sizeintegeryesMaximum size of transaction in bytes that the Payment Relay is willing to accept
vendor_iconstringnoVendor icon URL (JPG or PNG); wallet SHOULD use a placeholder when not provided
vendor_namestringyesVendor display name
vendor_addressstringnoVendor business address
vendor_urlstringnoVendor website URL
vendor_order_urlstringnoURL to view this order on vendor’s site
vendor_order_idstringnoVendor’s unique order identifier
order_referencestringnoShort customer-facing order identifier
notestringnoFree-text note from vendor to customer
totalstringyesTotal including fees and taxes, 8-DP string
feesstringnoFees subtotal, 8-DP string
taxesstringnoTaxes subtotal, 8-DP string
fiat_totalstringnoTotal in fiat currency, decimal string
fiat_taxstringnoTaxes in fiat currency, decimal string
fiat_currencystringconditionalISO 4217 currency code; required when fiat_total or fiat_tax is present
itemsarrayyesList of Connect Items
outputsarrayyesList 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)
}
FieldTypeRequiredDescription
typestringyesItemType enum
idstringyesUnique item ID or SKU
iconstringnoIcon URL (JPG or PNG); wallet SHOULD use a placeholder when not provided
namestringyesDisplay name
descstringnoItem description
countintegeryesNumber of units (>= 1)
unitstringyesUnit price, 8-DP string
totalstringyescount x unit, 8-DP string
taxstringnoTax 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
}
FieldTypeRequiredDescription
addressstringyesDogecoin address
amountstringyesAmount 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
}
FieldTypeRequiredDescription
idstringyesRelay-unique payment ID from Connect Payment
txstringyesHex-encoded signed Dogecoin transaction
refundstringnoCustomer-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_tokenstringconditionalOpaque 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"
}
FieldTypeRequiredDescription
idstringyesRelay-unique payment ID
statusstringyesPaymentStatus enum
reasonstringconditionalReason for decline; present when declined
txidstringconditionalHex-encoded tx ID; present when accepted or confirmed
confirmed_atstringconditionalRFC 3339 timestamp of when the tx reached the required block confirmations; present when confirmed
requiredintegerconditionalBlock confirmations required; present when accepted or confirmed
confirmedintegerconditionalCurrent block confirmations; present when accepted or confirmed
due_secintegerconditionalEstimated 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
}
FieldTypeRequiredDescription
idstringyesRelay-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"
}
FieldTypeRequiredDescription
errorstringyesErrorCode enum
messagestringyesHuman-readable error detail