Skip to content

Cheqi download links give a customer a hosted receipt URL when no wallet recipient is available. The same client-encrypted mechanism is used for an online download fallback and for a URL issued while the Cheqi API is temporarily unavailable.

https://receipt.cheqi.io/{downloadId}#{contentKey}

The SDK generates the download ID and AES-256-GCM content key locally. It builds the canonical download envelope locally, encrypts it, and uploads only ciphertext through POST /receipt/download. The fragment after # is not sent to Cheqi by the browser, so Cheqi cannot decrypt the receipt. Decryption happens in the customer's browser.

Use https://receipt.cheqi.io in production and https://sandbox.receipt.cheqi.io in sandbox.

Online fallback

Download fallback is enabled for the company or client application. When recipient resolution cannot find a wallet recipient and selects DOWNLOAD_FALLBACK, the SDK:

  1. generates the download URL and fragment key locally
  2. builds the canonical download envelope from the definitive receipt input
  3. encrypts that envelope locally
  4. uploads the ciphertext through POST /receipt/download
  5. returns the download URL to your integration

Your integration can render the URL as a QR code, print it, include it in an order confirmation, or show it on an order-status page.

When the API is reachable and recipient resolution returns routeFound: false, stop with customer-not-found. Do not call the low-level upload endpoint to bypass a disabled download fallback.

Service outage

The URL can also be generated without a network call. This lets a POS display or print the QR code while Cheqi is unavailable. The receipt page tells the customer that the receipt is not ready yet and asks them to return later.

Before exposing the URL, your integration must durably store:

  • the download URL or its download ID and content key
  • the original definitive receipt input and requested formats
  • issuer and jurisdiction context required for local generation
  • a stable merchant transaction identifier
  • the current processing state and operational timestamps

When Cheqi is reachable again, your processing builds and encrypts the canonical download envelope with the SDK, persists the ciphertext, and uploads it. Do not implement a separate offline receipt renderer or envelope format.

An already issued outage URL may be completed even if a later recipient resolution produces no route. POST /receipt/download deliberately takes no matchId and does not re-evaluate the fallback setting, because the customer already possesses the URL.

Retry contract

Your integration owns storage, retry scheduling, compute allocation, monitoring, retention, and manual recovery. Cheqi SDKs provide stateless primitives and return values; they do not include a database, outbox, background worker, or extensive retry policy.

Use these states:

StateStored dataNext action
PENDING_DOWNLOAD_TEMPLATEURL credentials and original receipt inputRetry local envelope generation
PENDING_DOWNLOAD_UPLOADURL credentials, template hash, and exact ciphertextRetry the exact stored upload bytes
DELIVERED_DOWNLOADDelivery result and URL referenceNo further submission

Persist ciphertext before attempting upload. If the upload result is ambiguous, retry the exact bytes. AES-GCM uses a random IV, so encrypting again produces different ciphertext.

The first successful write wins permanently. Replaying identical bytes as the same principal is idempotent. Different bytes or a different principal for the same download ID return HTTP 409 and never replace the stored receipt.

Never log the complete URL or fragment key. Anyone with the fragment key can decrypt the receipt.

Java SDK

For a custom local environment:

CheqiSDK sdk = CheqiSDK.builder()
    .customApiEndpoint("http://localhost:8080")
    .receiptDownloadBaseUrl("http://localhost:5190")
    .apiKey(System.getenv("CHEQI_API_KEY"))
    .maxRetries(0)
    .build();

The high-level flow completes a client-encrypted download immediately when the identification details provide enough local payment context:

ReceiptResult result = sdk.getReceiptService()
    .issueReceipt(identificationDetails, receiptPayload);

if (result.isAccepted() && result.getDownloadUrl() != null) {
    presentToCustomer(result.getDownloadUrl());
} else if (result.isDownloadEnvelopeRequired()) {
    ReceiptResult completed = sdk.getReceiptService().completeDownloadFallback(
        result,
        receiptEnvelope,
        templateHash,
        accessToken
    );
    presentToCustomer(completed.getDownloadUrl());
}

For an explicit cash or anonymous download flow, use issueDownloadReceipt(...) instead of recipient matching. The content key remains only in the returned URL fragment.