# Download links

A download link opens the receipt in the [Cheqi web wallet](/receipts/qr-collection).
This page explains how your integration creates, uploads, and retries that
link. You can share the returned URL in any customer-facing channel that suits
your checkout. The same encrypted link flow works online and when a URL is
issued while the Cheqi API is temporarily unavailable.

Use the complete receipt URL returned by the SDK. It includes the key needed
to open the receipt. Do not modify it, log it, or send it to anyone other than
the intended customer. Cheqi receives only the encrypted receipt body.

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

## Online fallback

Enable download fallback for the company or client application. When recipient
resolution selects `DOWNLOAD_FALLBACK`, the SDK creates and uploads the
encrypted receipt and returns its URL.

Your integration can show the URL on an order-status page, include it in an order
confirmation, or present it as a QR code at a physical checkout.
The same URL can open the Cheqi app directly on a verified device or remain in
the browser for a customer without the app. The merchant does not start a
pairing session or wait for the customer to open the link. See [Web Wallet](/receipts/qr-collection)
for the customer experience.
If the provider has no PAR or other reliable customer identifier, include its
card suffix in the encrypted `ReceiptPayload.paymentDetails.cardLastFour`. Four digits
alone do not identify a customer during merchant recipient resolution.

When the API is reachable and recipient resolution returns `routeFound: false`,
end the Cheqi flow and use your existing receipt process. 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 merchant
share the link while Cheqi is unavailable. The customer can open the receipt
after the encrypted upload completes.

Before exposing the URL, your integration must durably store:

- the download URL or its download ID and content key
- the original definitive `ReceiptPayload` and any optional identification details
- the issuer identity and jurisdiction data required for this receipt
- a stable merchant transaction identifier
- the current processing state and operational timestamps


When Cheqi is reachable again, use the SDK to build and encrypt the download
payload, persist the ciphertext, and upload it. Use the same SDK flow for
online and outage receipts.

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:

| State | Stored data | Next action |
|  --- | --- | --- |
| `PENDING_DOWNLOAD_TEMPLATE` | URL credentials and original receipt input | Retry local envelope construction and encryption |
| `PENDING_DOWNLOAD_UPLOAD` | URL credentials, template hash, and exact ciphertext | Retry the exact stored upload bytes |
| `DELIVERED_DOWNLOAD` | Delivery result and URL reference | No 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:

```java
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:

```java
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,
        RequestOptions.withAccessToken(accessToken)
    );
    presentToCustomer(completed.getDownloadUrl());
}
```

The other SDKs expose the same flow: `CompleteDownloadFallbackAsync` and
`IssueDownloadReceiptAsync` in [.NET](/sdk/dotnet#explicit-download-receipt),
`completeDownloadFallback` and `issueDownloadReceipt` in
[JavaScript](/sdk/javascript#explicit-download-receipt), and
`complete_download_fallback` and `issue_download_receipt` in
[Python](/sdk/python#explicit-download-receipt).

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