Behio Storefront SDK
Customer

Digital Downloads

Deliver purchased digital files (PDF, MP3, e-book) and let customers download them

Overview

A product can carry one or more digital assets (PDF, MP3, e-book, software) that the buyer receives automatically once the order is paid. When an order transitions to paid (an online gateway confirms payment, an admin marks it paid, or a gift card fully covers the total), Behio queues a download grant for every active digital asset of the purchased products and attempts delivery immediately. Unpaid orders (cash on delivery, bank transfer before payment) never receive grants.

Grants enforce an optional per-customer download limit (maxDownloads) and expiry (expiresAfterDays, counted from the grant). Files are served through short-lived signed URLs. The server checks the budget before issuing each URL; an already issued URL remains usable until its own expiry.

Delivery in progress

OrderDetail.contentDeliveryPending and CheckoutResult.contentDeliveryPending are true when payment is recorded but file or course access is still being assigned. Render the pending notice in the initial server response and periodically refresh the authenticated order detail. Keep existing downloads visible and explain that payment was received and no second order is needed. A pending order is also marked in the admin order detail.

The work record is committed in the same database transaction as payment. A worker checks due records each minute, retries failures with backoff up to 30 minutes, and recovers expired processing leases after a restart. Repeated attempts do not reset download limits, extend access or duplicate course enrollment. Full refunds and cancellations pause delivery and revoke order-based access. The API does not expose private file URLs or internal delivery errors in this status.

List the customer's downloads

Requires customer authentication. Returns every grant across all the customer's orders:

const { data } = await client.customer.getDownloads();
// {
//   items: [{
//     id: string,
//     orderId: string | null,
//     fileName: string,
//     productName: string | null,   // localized, for display
//     productSlug: string | null,
//     fileSize: number,             // bytes
//     mimeType: string,
//     version: string | null,
//     downloadCount: number,
//     maxDownloads: number | null,       // null = unlimited
//     remainingDownloads: number | null, // null = unlimited
//     lastDownloadAt: number | null,
//     expiresAt: number | null,          // epoch ms, null = never
//     isExpired: boolean,
//     isMaxedOut: boolean,
//     createdAt: number,
//   }]
// }

Get a signed download URL

Mint a short-lived (15 min) signed URL for one grant. This counts against the download budget and is rejected (403) once the grant is expired or maxed out:

const { data, error } = await client.customer.getDownloadUrl(downloadId);
// data: { url, fileName, mimeType, fileSize, remainingDownloads, expiresAt }
if (data) window.open(data.url, "_blank");

Check isExpired / isMaxedOut on the grant to disable the download button before calling.

Order detail

Paid order details carry the same grants inline, so you can show a download section on the order / confirmation page without a second call:

const { data: order } = await client.orders.get(orderNumber);
order?.downloads; // DigitalDownload[], empty for unpaid or physical-only orders

Product badge

The product detail exposes isDigital: boolean, true when the product has at least one active digital asset. Use it to render an "instant delivery" badge on the PDP.

Guest orders

A guest who verified an order via the order-access flow can mint a download URL with the order-access token, scoped to that one order:

const { data } = await client.orders.getAccessDownloadUrl(accessToken, downloadId);

Admin

Merchants attach digital assets to a product in the admin, or via MCP (eshop-digital-asset-create, eshop-digital-assets-list, eshop-digital-asset-update, eshop-digital-asset-delete) with fileUrl (an R2/S3 object), fileSize, mimeType, and optional maxDownloads + expiresAfterDays.

Limits, revoked access and recovery

DigitalDownload.isRevoked is true when an asset is inactive or an associated order no longer grants access. Render it as unavailable alongside isExpired and isMaxedOut. Manual grants without an order remain supported. The API rechecks the current status when issuing a download URL; a previous page render does not authorize a later download. Full refunds and cancellations block access.

Concurrent requests share the same allowance: a grant with two downloads left can issue at most two successful URLs. Refresh the displayed allowance after a successful request. Failure to sign a private file does not consume the limit and never returns an unsigned fallback URL. External file hosts must use HTTPS. Already issued signed URLs retain their own short validity period.

Open a download window during the click gesture, then navigate it after the server authorizes the request. Opening a new window only after an asynchronous server response can trigger popup protection. Keep authorization tokens out of the new window URL and refresh the account/receipt state after success.

New storefront orders retain the purchased listing identity for files and courses, including bundle components and promotional free units. Sharing a warehouse item does not grant another listing's content. Historical orders without that identity resolve only when the warehouse item has one unambiguous listing in the same shop. Existing grants retain their access history; ambiguous legacy orders require merchant review.

On this page