Binary File Uploads
Upload encrypted binary files (images, documents) to Welshare. Files are AES-256-GCM encrypted client-side before being uploaded to the storage backend (S3 compatible). Metadata is stored on Nillion.
Installation
Choose your integration approach:
Option A: React Applications (Recommended)
This approach manages user accounts and upload credentials inside a browser frame running on an official welshare domain. Your application does not have to care about any cryptography at all. You just provide data upload interfaces. Your application and the frame communicate with postMessage commands.
npm install @welshare/react
# or
pnpm add @welshare/react
Requires React 19+ as a dependency
Option B: SDK Only (Node.js / Frontends managing wallets / signers on their own)
If you're managing wallet connections and user keys inside your own app, the SDK is right for you. You identify users and manage their wallet connections, data is encrypted on your app and you call the welshare delegation and storage endpoints directly.
npm install @welshare/sdk
# or
pnpm add @welshare/sdk
Configuration
Register your application at wallet.welshare.app/application to get an applicationId.
import { resolveEnvironment } from "@welshare/sdk";
// Use a named environment
const welshareEnvironment = resolveEnvironment("production"); // or "staging", complete custom nillion configs *are* possible.
Submission Flows
Option A: using the external wallet popup dialog (useWelshare)
import { useWelshare } from "@welshare/react";
function PhotoUpload({ questionnaireId }) {
const { uploadFile, isConnected, openWallet } = useWelshare({
applicationId: "your-app-id",
environment: welshareEnvironment,
callbacks: {
onFileUploaded: (uid, url) => console.log("Uploaded:", uid),
onError: (error) => console.error(error),
},
});
const handleUpload = async (file: File) => {
if (!isConnected) {
openWallet();
return;
}
const { url, binaryFileUid } = await uploadFile(
file,
`questionnaire/${questionnaireId}/photo`
);
// Use in QuestionnaireResponse
return {
valueAttachment: {
id: binaryFileUid,
url,
contentType: file.type,
size: file.size,
title: file.name,
},
};
};
return (
<input type="file" onChange={(e) => handleUpload(e.target.files[0])} />
);
}
Option B: Direct Keypair Access (useBinaryUploads + SDK)
import { useBinaryUploads, encryptAndUploadFile } from "@welshare/react";
import { WelshareApi, resolveEnvironment } from "@welshare/sdk";
// Or use lightweight encryption-only import:
// import { ... } from "@welshare/sdk/encryption";
/// @param applicationId: you can register applications on {staging}.wallet.welshare.app/application
function useDirectUpload(keypair: Keypair, applicationId: string) {
const { createUploadCredentials, downloadAndDecryptFile, isRunning, error } =
useBinaryUploads({
keypair,
environment: welshareEnvironment,
});
/// reference can point to another document on the welshare space that this binary file is "attached" to or related with.
/// References usually contain a document type or a context in which they are used, eg `questionnaire/{uuid}/facial-photo`
const uploadFile = async (file: File, reference: string) => {
// 1. Get presigned S3 URL
const { presignedUrl, uploadKey } = await createUploadCredentials({
applicationId,
reference,
fileName: file.name,
fileType: file.type,
});
// 2. Encrypt and upload to storage backend / S3
const { encryptionKey } = await encryptAndUploadFile(file, presignedUrl);
// 3. Store metadata and keys on Nillion (keys are encrypted across nodes)
const { insertedUid, errors } = await WelshareApi.submitBinaryData(
keypair,
{
encryption_key: JSON.stringify(encryptionKey),
reference,
file_name: file.name,
file_size: file.size,
file_type: file.type,
controller_did: keypair.toDidString(),
// when prefixed with "welshare://" this resolves to this gets resolved to urls of welshare's storage backend
url: `welshare://${uploadKey}`,
},
environment: welshareEnvironment,
applicationId
);
return { insertedUid, url: `welshare://${uploadKey}` };
};
return { uploadFile, downloadAndDecryptFile, isRunning, error };
}
Downloading Files
Option A
As you don't control your users' keys in this option, you can't use storage keypairs that are required to authenticate your users' requests.
Option B
import { WelshareApi } from "@welshare/sdk";
import { decrypt } from "@welshare/sdk/encryption";
// 1. Read binary file entry from Nillion (contains the decrypted key) & fetch data as a Promise<ArrayBuffer>
const { binaryFile, data } = await WelshareApi.fetchBinaryData(
keypair,
environment,
binaryFileUid
);
// 2. Download and decrypt
const encryptedData = await data; // this downloads the data
const decryptedData = await decrypt(
encryptedData,
JSON.parse(binaryFile.encryption_key)
);
Downloading Files as Application
tbd . Right now this requires signing off an application control key jwt to authenticatte with a welshare API that grants access to user records after verifying access control. This flow will be published very soon.
Integration with Questionnaires
Uploaded binary files can be part of FHIR QuestionnaireResponses's valueAttachment items:
const questionnaireResponse = {
resourceType: "QuestionnaireResponse",
questionnaire: questionnaireId,
status: "completed",
item: [
{
linkId: "photo-upload",
answer: [
{
valueAttachment: {
id: <binaryFileUid>, // From upload
url: <uploadedUrl>, // welshare://... URL
contentType: "image/jpeg",
size: 102400,
title: "profile-photo.jpg",
},
},
],
},
],
};
Use QuestionnaireResponseSchema.findValueAttachments(response.item) to extract all attachments from a response.
Reference
Binary Files Collection Schema UID: 9d696baf-483f-4cc0-b748-23a22c1705f5
Encryption
- Algorithm: AES-256-GCM
- Key size: 256 bits
- IV size: 96 bits (12 bytes)
Keys are stored with Nillion's %allot modifier for secret sharing.
Import Options:
Encryption utilities can be imported from the main SDK package or from a lightweight entry point:
// Full SDK (includes Nillion dependencies)
import { encryptFile, decrypt, type EncryptionKey } from "@welshare/sdk";
// Lightweight encryption-only (no Nillion dependencies)
import { encryptFile, decrypt, type EncryptionKey } from "@welshare/sdk/encryption";
Use the /encryption import when you only need file encryption/decryption without other SDK features to reduce bundle size.
Presigned URLs
- Expiry: 15 seconds
- Format:
{network}/user-uploads/{did}/{timestamp}-{fileName}
API Endpoints
| Endpoint | Purpose |
|---|---|
POST /auth/delegate/storage?requestType=write | Get upload presigned URL |
POST /auth/delegate/storage?requestType=read | Get download presigned URL |
POST /auth/delegate/storage?requestType=delete | Delete file |
Related
- Questionnaires - Creating questionnaires
- Authentication - Session keys and JWT
- SDK API Docs - Full SDK reference