
Research
Shai-Hulud Descends to Hades: Miasma Worm Campaign Spreads with New PyPI Wave
Socket found 37 malicious PyPI wheels that abuse Python startup hooks to launch a Bun-powered credential stealer tied to Mini Shai-Hulud/Miasma.
react-native-docusign
Advanced tools
React Native / Expo native module wrapping DocuSign iOS and Android SDKs for in-app captive signing
React Native / Expo native module that wraps the official DocuSign iOS and Android SDKs, allowing apps to present the native DocuSign signing UI directly in-app (captive signing).
Built on the Expo Modules API. Works with Expo SDK 55+ (managed or bare with expo prebuild) and Expo Dev Client. Also usable in non-Expo React Native projects after running npx install-expo-modules to install expo-modules-core; in that case, run expo prebuild so the Config Plugin can fetch the DocuSign sdk-pdf AAR and inject the iOS / Android build settings, otherwise you must replicate those steps by hand.
Unofficial package. This is a community wrapper. It is not affiliated with, endorsed by, or sponsored by DocuSign, Inc. or any other organization. "DocuSign" is a trademark of DocuSign, Inc. The MIT license covers this wrapper's source code only. Consumers are responsible for accepting DocuSign's Mobile SDK License Agreement separately when integrating with DocuSign's services.
| Flow | iOS | Android |
|---|---|---|
Session flow: initialize → loginWithAccessToken → presentCaptiveSigning | ✅ | ✅ |
URL flow: presentCaptiveSigningWithUrl | ✅ | ❌ |
The DocuSign Android SDK (2.1.4) does not expose a public URL-based signing entry point; calling presentCaptiveSigningWithUrl on Android rejects with not_implemented. Default to the session flow for cross-platform apps.
Both iOS and Android consume the same 13-field session payload from your backend. There is no platform-specific branching on the client. Mint it once on your BFF, hand it to the module, and the same code path runs on both OSes.
The mobile module expects exactly these fields:
integratorKey, environment, accessToken, expiresIn, host, accountId, userId, userName, email, envelopeId, clientUserId, recipientName, recipientEmail.
Full schema and BFF reference implementation: docs/BACKEND_GUIDE.md → Session Payload Contract.
At a high level, a signing flow looks like this:
initialize() once (on app start or on-demand) to configure the DocuSign SDK.loginWithAccessToken() with the session data from your backend.presentCaptiveSigning(). The DocuSign SDK takes over the screen and presents its own native signing UI.The React Native layer never renders any of the signing UI. It only triggers the flow, passes credentials to the SDK, and listens for completion events.
| Platform | Minimum OS | SDK version | Language |
|---|---|---|---|
| iOS | 15.1 | DocuSign iOS SDK 4.1.1 | Swift 5.9 |
| Android | API 24 (Android 7.0) | DocuSign Android SDK 2.1.4 | Kotlin 1.8+ |
Runtime requirements:
The DocuSign native SDKs are NOT bundled inside this npm package. They are declared as external dependencies and resolved at consumer build time:
pod 'DocuSign-iOS-SDK', '~> 4.1.1' from the public CocoaPods trunkcom.docusign:androidsdk:2.1.4 from DocuSign's Maven repository (the config plugin adds the repo automatically)DocuSign's com.docusign:sdk-pdf:2.1.4 AAR ships a pre-generated com.bumptech.glide.GeneratedAppGlideModuleImpl.class that collides at dex time with any other Glide-based library in the host app (notably expo-image, react-native-fast-image, and similar). To avoid this without redistributing DocuSign's binary, the Expo Config Plugin downloads sdk-pdf-2.1.4.aar directly from DocuSign's public Maven during expo prebuild, verifies its SHA-256 against a pinned hash, removes the offending class from the AAR's classes.jar in-memory, and writes the stripped artifact to node_modules/react-native-docusign/android/libs/. The existing flatDir injection picks it up at consumer build time. The download is cached after the first run; corrupted or partial caches are detected and regenerated. SHA mismatch or fetch failure aborts expo prebuild with an actionable error rather than silently letting the Android build fail later at the dex step.
Estimated package size: under 1 MB.
npm install react-native-docusign
# or
yarn add react-native-docusign
# or
pnpm add react-native-docusign
In app.config.ts (or app.json):
export default {
expo: {
// ...
plugins: [
[
'react-native-docusign',
{
cameraPermission: 'Allows you to sign DocuSign documents using your camera.',
photoPermission: 'Allows you to attach photos to DocuSign documents.',
},
],
],
},
};
npx expo prebuild --clean
npx expo run:ios
npx expo run:android
If you are not using Expo prebuild, you must manually:
pod 'DocuSign-iOS-SDK', '~> 4.1.1' to your Podfile and run pod installandroid/build.gradle + android/app/build.gradleNSCameraUsageDescription and NSPhotoLibraryUsageDescription keys to your Info.plistAndroidManifest.xmlSee the Permissions section for exact values.
The config plugin accepts the following props:
type DocuSignPluginProps = {
/**
* Value written to NSCameraUsageDescription in Info.plist.
* Defaults to a generic DocuSign-appropriate message.
*/
cameraPermission?: string;
/**
* Value written to NSPhotoLibraryUsageDescription in Info.plist.
* Defaults to a generic DocuSign-appropriate message.
*/
photoPermission?: string;
/**
* URL of the Android Maven repository that serves the DocuSign Android SDK.
* Defaults to 'https://maven.docusign.com/'.
* Override only if DocuSign moves their repo.
*/
androidMavenRepo?: string;
};
What the plugin does during expo prebuild:
NSCameraUsageDescription and NSPhotoLibraryUsageDescription to Info.plistINTERNET, ACCESS_NETWORK_STATE, and CAMERA permissions to AndroidManifest.xmlbuild.gradle under allprojects { repositories { ... } }import * as DocuSign from 'react-native-docusign';
async function signAgreement() {
// Step 1: configure the SDK (call once per app lifetime)
await DocuSign.initialize({
integratorKey: 'YOUR_INTEGRATOR_KEY',
environment: 'demo', // or 'production'
});
// Step 2: fetch a signing session from your backend
const session = await fetch('/api/envelopes/signing-session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ documentId: 'doc_123', requestType: 'standard' }),
}).then((r) => r.json());
// Step 3: authenticate the SDK
await DocuSign.loginWithAccessToken({
accessToken: session.accessToken,
accountId: session.accountId,
userId: session.userId,
userName: session.userName,
email: session.email,
host: session.host, // 'https://demo.docusign.net/restapi'
});
// Step 4: present the native signing UI
const result = await DocuSign.presentCaptiveSigning({
envelopeId: session.envelopeId,
recipientUserName: session.userName,
recipientEmail: session.email,
recipientClientUserId: session.recipientClientUserId,
});
switch (result.status) {
case 'completed':
console.log('Signed:', result.envelopeId);
break;
case 'cancelled':
console.log('User cancelled signing');
break;
case 'error':
console.error('Signing error:', result.errorMessage);
break;
}
}
initialize(config: DocuSignConfig): Promise<void>Configures the underlying DocuSign SDK. Must be called once before any other function. Safe to call multiple times (subsequent calls are no-ops).
type DocuSignConfig = {
integratorKey: string;
environment: 'demo' | 'production';
};
Parameters:
integratorKey: your DocuSign Integrator Key (Client ID). Can be fetched from your backend at runtime to avoid shipping it in the app bundle.environment: 'demo' targets demo.docusign.net (DocuSign developer sandbox). 'production' targets docusign.net.Throws: rejects if the SDK cannot be initialized.
loginWithAccessToken(params: DocuSignAuthParams): Promise<void>Authenticates the SDK with a short-lived access token obtained from your backend (via DocuSign JWT Grant).
type DocuSignAuthParams = {
accessToken: string;
accountId: string;
userId: string;
userName: string;
email: string;
host: string;
};
Parameters:
accessToken: OAuth 2.0 access token from DocuSignaccountId: DocuSign account ID (GUID)userId: DocuSign user ID (GUID)userName: display name for the signeremail: email address for the signerhost: DocuSign API host URL (e.g. 'https://demo.docusign.net/restapi')Throws: rejects with login_failed if the token is invalid, expired, or rejected by DocuSign.
Notes: access tokens from DocuSign are typically valid for 1 hour. Do not cache them client-side. Fetch a fresh token for each signing session.
presentCaptiveSigning(params: CaptiveSigningParams): Promise<SigningResult>Presents the native DocuSign signing UI on top of your React Native app. The returned promise resolves when the user completes or cancels signing.
type CaptiveSigningParams = {
envelopeId: string;
recipientUserName: string;
recipientEmail: string;
recipientClientUserId: string;
};
type SigningResult = {
status: 'completed' | 'cancelled' | 'error';
envelopeId: string;
errorCode?: string;
errorMessage?: string;
};
Parameters:
envelopeId: the DocuSign envelope ID created by your backendrecipientUserName, recipientEmail: must match the recipient registered on the enveloperecipientClientUserId: the clientUserId of the embedded recipient, used by DocuSign to identify captive signersThrows: rejects with signing_failed if the SDK fails to present the signing UI (e.g. not initialized, not logged in, invalid envelope).
Returns: resolves with a SigningResult once the user completes or cancels. status === 'completed' means the user finished the signing ceremony. status === 'cancelled' means the user explicitly cancelled or closed the signing UI.
presentCaptiveSigningWithUrl(params: CaptiveSigningUrlParams): Promise<SigningResult>iOS only. Presents the DocuSign signing UI using a pre-minted recipient view URL (obtained server-side from POST /envelopes/{id}/views/recipient). Bypasses SDK authentication; no initialize / loginWithAccessToken required first.
type CaptiveSigningUrlParams = {
signingUrl: string;
envelopeId: string;
recipientId?: string;
};
Parameters:
signingUrl: short-lived recipient view URL from your backend (~5 min TTL, single use)envelopeId: the DocuSign envelope IDrecipientId (optional): identifier used for event correlationThrows:
not_implemented on Android (see Limitations)signing_failed if the URL is expired, malformed, or rejected by DocuSignReturns: same SigningResult shape as presentCaptiveSigning.
Why this flow: keeps the DocuSign access token out of the mobile app entirely; your backend mints the signing URL and hands that single-use credential to the client. Recommended for production iOS flows.
logout(): Promise<void>Logs out of the DocuSign SDK and clears any cached credentials. Call this when the user signs out of your app.
isLoggedIn(): Promise<boolean>Returns the current login state. Useful for deciding whether to skip the login step on a subsequent signing attempt.
endSigningSession(): Promise<void>Tears down any in-flight signing session and the underlying SDK auth state so the next loginWithAccessToken + presentCaptiveSigning pair starts from a clean slate. Safe to call when no session is active.
Call this between captive signing flows (e.g. inside the finally block of your orchestrator) when running multiple envelopes in the same app session. Without it, the package falls back to an implicit teardown inside performLogin which has historically (≤ 1.0.1) raced on iOS and caused the captive signing UI to hang on a spinner on the second consecutive open. The 1.0.2 fix sequences that teardown correctly, but calling endSigningSession explicitly remains the cleanest pattern.
The useDocuSignSigning hook's reset() calls this automatically when the SDK has been initialized.
reset(): Promise<void>Full SDK teardown. Heavier counterpart to endSigningSession: resolves any in-flight signing promise as cancelled, wipes WebKit data on iOS (cookies, service workers, fetch cache, IndexedDB, etc.), calls logout(), removes notification observers (iOS), and flips the internal isInitialized flag to false so the next initialize() call re-runs the underlying SDK setup against a fresh state.
Use this when you want a hard reset between flows (error recovery, switching DocuSign accounts, after an app-level logout). For routine teardown between consecutive captive signing flows on the same auth, prefer endSigningSession, which keeps the SDK initialized and skips the observer churn.
Safe to call when the SDK was never initialized: returns immediately without touching the underlying SDK.
| Use case | Recommended call |
|---|---|
| Between captive signing flows in the same app session | endSigningSession |
| After an error you want to recover from | reset |
| Switching DocuSign accounts / integrator keys | reset |
| App-level logout | reset |
In addition to the promise-based API, you can subscribe to events. Events fire for every signing result, including those triggered from other parts of your app.
const completeSub = DocuSign.addSigningCompleteListener((event) => {
console.log('Signed envelope:', event.envelopeId);
});
const cancelSub = DocuSign.addSigningCancelledListener((event) => {
console.log('Cancelled:', event.envelopeId, event.reason);
});
const errorSub = DocuSign.addSigningErrorListener((event) => {
console.error('Error:', event.errorCode, event.errorMessage);
});
// Later, clean up:
completeSub.remove();
cancelSub.remove();
errorSub.remove();
In React hooks:
import { useEffect } from 'react';
import * as DocuSign from 'react-native-docusign';
useEffect(() => {
const sub = DocuSign.addSigningCompleteListener((event) => {
refetchEnvelopeStatus(event.envelopeId);
});
return () => sub.remove();
}, []);
For React consumers, the package exports a useDocuSignSigning hook that wraps the SDK lifecycle, the state machine, and event subscription so you do not have to implement them yourself.
import { useDocuSignSigning } from 'react-native-docusign';
function SigningScreen() {
const { state, error, result, startSigning } = useDocuSignSigning({
config: {
integratorKey: 'YOUR_INTEGRATOR_KEY',
environment: 'demo',
},
});
async function handleSign() {
// Fetch the signing session from your backend
const session = await fetchSigningSessionFromYourBackend();
// Hook handles loginWithAccessToken + presentCaptiveSigning + state transitions
const result = await startSigning(session);
if (result.status === 'completed') {
// Navigate to confirmation
}
}
return (
<View>
{state === 'initializing' && <Text>Setting up DocuSign...</Text>}
{state === 'ready' && <Button title="Sign document" onPress={handleSign} />}
{state === 'preparing' && <ActivityIndicator />}
{state === 'signing' && <Text>Opening signing UI...</Text>}
{state === 'completed' && <Text>Signed envelope {result?.envelopeId}</Text>}
{state === 'cancelled' && <Text>Signing cancelled</Text>}
{state === 'error' && <Text>Error: {error?.message}</Text>}
</View>
);
}
initialize() automatically on mount (toggle with autoInitialize: false if you want to defer)startSigning(session):
{ type: 'session', ... }: runs loginWithAccessToken + presentCaptiveSigning (iOS + Android){ type: 'url', ... }: runs presentCaptiveSigningWithUrl (iOS only; throws on Android)error fieldconst result = await startSigning({
type: 'url',
signingUrl: sessionFromBackend.signingUrl,
envelopeId: sessionFromBackend.envelopeId,
});
const result = await startSigning({
type: 'session',
accessToken: sessionFromBackend.accessToken,
accountId: sessionFromBackend.accountId,
userId: sessionFromBackend.userId,
userName: sessionFromBackend.userName,
email: sessionFromBackend.email,
host: sessionFromBackend.host,
envelopeId: sessionFromBackend.envelopeId,
recipientUserName: sessionFromBackend.userName,
recipientEmail: sessionFromBackend.email,
recipientClientUserId: sessionFromBackend.recipientClientUserId,
});
startSigningstartSigning again with a fresh session)idle ──auto-init──> initializing ──> ready ──startSigning──> preparing ──> signing ──> completed
│ │
│ └─> cancelled
│ │
└────────────── error <───── any failure ──────── └─> error
type DocuSignSigningState =
| 'idle' // hook just mounted, initialization not started
| 'initializing' // initialize() in progress
| 'ready' // SDK ready, can call startSigning
| 'preparing' // loginWithAccessToken in progress
| 'signing' // native signing UI is presented
| 'completed' // user finished signing successfully
| 'cancelled' // user cancelled signing
| 'error'; // any failure
type SigningSessionWithAuth = DocuSignAuthParams & CaptiveSigningParams & { type: 'session' };
type SigningSessionWithUrl = CaptiveSigningUrlParams & { type: 'url' };
type SigningSession = SigningSessionWithAuth | SigningSessionWithUrl;
type UseDocuSignSigningOptions = {
config: DocuSignConfig;
autoInitialize?: boolean; // default true
};
type UseDocuSignSigningReturn = {
state: DocuSignSigningState;
error: Error | null;
result: SigningResult | null;
initialize: () => Promise<void>; // manual init if autoInitialize=false
startSigning: (session: SigningSession) => Promise<SigningResult>;
reset: () => void; // back to idle/ready, clears error and result
};
If you need fine-grained control over the SDK lifecycle (e.g., re-using login across multiple screens, or custom error recovery flows), use the lower-level functions directly: initialize, loginWithAccessToken, presentCaptiveSigning. The hook is opinionated and may not fit every flow.
export type DocuSignEnvironment = 'demo' | 'production';
export type DocuSignConfig = {
integratorKey: string;
environment: DocuSignEnvironment;
};
export type DocuSignAuthParams = {
accessToken: string;
accountId: string;
userId: string;
userName: string;
email: string;
host: string;
};
export type CaptiveSigningParams = {
envelopeId: string;
recipientUserName: string;
recipientEmail: string;
recipientClientUserId: string;
};
export type SigningStatus = 'completed' | 'cancelled' | 'error';
export type SigningResult = {
status: SigningStatus;
envelopeId: string;
errorCode?: string;
errorMessage?: string;
};
export type SigningCompleteEvent = {
envelopeId: string;
};
export type SigningCancelledEvent = {
envelopeId: string;
reason?: string;
};
export type SigningErrorEvent = {
envelopeId?: string;
errorCode: string;
errorMessage: string;
};
This package does NOT implement DocuSign JWT Grant authentication. JWT Grant must happen on your backend because it requires access to an RSA private key that should never ship on mobile devices.
Recommended flow:
Mobile app Your backend DocuSign
---------- ------------ --------
1. Request signing --->
2. JWT Grant ------------->
3. Access token <----------
4. Create envelope -------->
5. Envelope ID <----------
<-- Signing session data
6. initialize()
7. loginWithAccessToken()
8. presentCaptiveSigning()
[Native SDK UI]
9. Complete/cancel event
10. Confirm status --->
11. Verify via webhook ---->
12. Confirmed <---------
The mobile app never stores DocuSign tokens. Access tokens are fetched per signing session, used immediately, and discarded. Your backend must perform authorization checks before issuing a signing session for a given envelope.
For information on DocuSign JWT Grant auth on the backend, see https://developers.docusign.com/platform/auth/jwt/.
The SDK itself does not provide an API to prefill document fields. Prefilling happens server-side when the envelope is created.
When your backend calls DocuSign's REST API POST /accounts/{accountId}/envelopes, it should set tab values on the envelope:
textTabs: text fieldscheckboxTabs: checkboxesdateTabs: date pickersdropdownTabs (alias listTabs): dropdown selectionssignHereTabs: signature placement (leave empty for user to sign)initialHereTabs: initial placementWhen the mobile app then calls presentCaptiveSigning(), the user will see the document with all server-prefilled fields already populated. They only need to fill remaining fields and sign.
Example (backend, pseudo-code):
const envelope = await docusign.envelopes.create({
templateId: 'template_agreement_v1',
templateRoles: [
{
email: 'user@example.com',
name: 'Jane Doe',
roleName: 'signer',
clientUserId: 'user_123',
tabs: {
textTabs: [
{ tabLabel: 'request_type', value: 'standard' },
{ tabLabel: 'counterparty', value: 'Acme Corp.' },
{ tabLabel: 'mailing_address', value: '123 Main St, Anytown, USA' },
],
dateTabs: [{ tabLabel: 'effective_date', value: '2026-05-01' }],
},
},
],
status: 'sent',
});
Because the DocuSign SDK returns control to your app after each signing ceremony, you can easily chain multiple signings in sequence:
async function signMultipleDocuments(documents: Document[]) {
const results: SigningResult[] = [];
for (const document of documents) {
const session = await fetchSigningSession(document.id);
await DocuSign.loginWithAccessToken(session);
const result = await DocuSign.presentCaptiveSigning({
envelopeId: session.envelopeId,
recipientUserName: session.userName,
recipientEmail: session.email,
recipientClientUserId: session.recipientClientUserId,
});
results.push(result);
if (result.status !== 'completed') break;
}
return results;
}
Between signings, you can also show a confirmation prompt ("sign another document?") and only continue if the user agrees.
The module rejects promises with coded exceptions you can inspect at the call site:
| Error code | When | Mitigation |
|---|---|---|
initialize_failed | SDK failed to configure | Check integrator key and network connectivity |
login_failed | Access token rejected, or Keychain misconfigured (iOS) | Fetch a fresh token from your backend; on iOS also verify AppIdentifierPrefix is set in Info.plist (see Permissions) |
signing_failed | SDK failed to present or complete signing | Check envelope ID, recipient info, SDK login state |
not_initialized | initialize() was not called first | Call initialize() before any other method |
not_logged_in | loginWithAccessToken() was not called first | Call loginWithAccessToken() before presentCaptiveSigning() |
Example:
try {
await DocuSign.presentCaptiveSigning(params);
} catch (error) {
if (error.code === 'not_logged_in') {
const session = await refreshSession();
await DocuSign.loginWithAccessToken(session);
// retry
} else {
reportError(error);
}
}
signature scope. Do not request broader scopes (impersonation, etc.) unless strictly necessary.Info.plistThe config plugin writes these keys automatically, or you can configure them manually:
| Key | Purpose |
|---|---|
NSCameraUsageDescription | Required when users capture signatures or attach photos via camera |
NSPhotoLibraryUsageDescription | Required when users attach photos from the photo library |
AppIdentifierPrefix | Required. Enables the DocuSign iOS SDK to store auth tokens in the Apple Keychain. Without this key, loginWithAccessToken fails with login_failed / unauthorized even when the token is valid. Set to $(AppIdentifierPrefix); Xcode resolves this to your team ID prefix at build time. |
AndroidManifest.xml| Permission | Purpose |
|---|---|
android.permission.INTERNET | DocuSign API calls |
android.permission.ACCESS_NETWORK_STATE | DocuSign SDK network monitoring |
android.permission.CAMERA | Signature and photo capture |
Ensure pod install completed successfully inside ios/. The podspec declares a dependency on DocuSign-iOS-SDK; if CocoaPods trunk is unreachable, the pod cannot be installed. Check your network, proxy, and CocoaPods version (pod --version should be 1.14+).
The DocuSign SDK must be set to use_frameworks! mode. Most Expo projects use frameworks by default. If you recently switched to static libraries, add use_frameworks! :linkage => :static to your Podfile and rebuild.
The DocuSign Android SDK is not on Maven Central; it is hosted on DocuSign's own Maven repository. Ensure the config plugin has added the repo to your project-level build.gradle. If running a bare project, manually add:
allprojects {
repositories {
maven { url "https://maven.docusign.com/" }
}
}
presentCaptiveSigningThe SDK login state is in-memory and does not survive app restarts. Always call loginWithAccessToken() before presentCaptiveSigning() within the same app session.
DocuSign access tokens are valid for about 1 hour. If a token expires while the signing UI is open, the SDK emits an error event and the promise rejects. Catch the error, fetch a fresh session from your backend, and retry.
login_failed on iOS even with a valid tokenIf loginWithAccessToken rejects with login_failed (or the SDK logs "unauthorized") but the same token works on Android, the most likely cause is a missing AppIdentifierPrefix entry in your Info.plist.
The DocuSign iOS SDK uses the Apple Keychain to store auth state. Without AppIdentifierPrefix, it cannot access the Keychain group and rejects the login at the SDK level, regardless of token validity.
Fix: add this entry to your Info.plist (or to ios.infoPlist in app.config.ts for Expo projects):
<key>AppIdentifierPrefix</key>
<string>$(AppIdentifierPrefix)</string>
Xcode resolves $(AppIdentifierPrefix) to your Apple Developer team ID prefix at build time. A native rebuild (expo prebuild + expo run:ios) is required after adding the key.
The SDK presents its UI on the topmost view controller. If you are presenting from within a modal, the module walks the presented view controller chain. If signing still does not appear, call presentCaptiveSigning() after any existing modals have been dismissed.
The module uses appContext.activityProvider.currentActivity to get the current activity. Ensure presentCaptiveSigning() is called while the app is in the foreground.
| This package version | Expo SDK | React Native | iOS SDK | Android SDK |
|---|---|---|---|---|
| 1.0.x | 55.x | 0.82.x | DocuSign iOS 4.1.1 | DocuSign Android 2.1.4 |
presentCaptiveSigningWithUrl is supported on iOS via DocuSign's iOS SDK 4.1.1. The DocuSign Android SDK 2.1.4 does not expose a public API that accepts a pre-minted recipient view URL, so the method throws not_implemented on Android. On Android, use loginWithAccessToken + presentCaptiveSigning (the session path). Your backend can branch response shape per platform.com.docusign:sdk-offline-signing. This package does not currently expose offline APIs. If you need offline signing, open an issue or send a pull request.No. Expo Go does not support custom native modules. You must use a development build (expo-dev-client) or bare React Native.
Not directly. SSO should be handled on your backend. Exchange the SSO session for a DocuSign access token via JWT Grant, then pass the access token to loginWithAccessToken().
presentCaptiveSigning() without logging in first?No. You must call initialize() then loginWithAccessToken() before presentCaptiveSigning(). This is a DocuSign SDK constraint, not a limitation of this wrapper.
Not directly. After signing completes, use the DocuSign REST API from your backend to download the completed envelope PDF (GET /accounts/{accountId}/envelopes/{envelopeId}/documents/combined).
Create a free DocuSign developer account at https://developers.docusign.com. It gives you access to the demo.docusign.net sandbox, which you can target by setting environment: 'demo' in initialize().
Configure a DocuSign Connect webhook that points to your backend. DocuSign will push envelope status updates (sent, delivered, completed, declined, voided) as they happen. Use the webhook as your source of truth and treat the mobile-side SigningResult as a UX signal only.
The DocuSign native SDKs include a full PDF viewer, signature pad, form renderer, and offline sync engine. Combined, they add roughly 30 to 50 MB per platform to your app bundle. DocuSign reduced the iOS SDK size by about 75% in v4.0.0; the current size is considered the minimum viable footprint.
react-native-pdf or another PDF viewer?Yes. The DocuSign SDK's internal PDF viewer only renders inside the signing ceremony. It does not conflict with other PDF viewers.
Yes, actively. See CHANGELOG.md for release history.
MIT. See LICENSE.
This package is not affiliated with or endorsed by DocuSign, Inc. "DocuSign" is a trademark of DocuSign, Inc. The DocuSign native SDKs are distributed separately under their own license terms.
FAQs
React Native / Expo native module wrapping DocuSign iOS and Android SDKs for in-app captive signing
The npm package react-native-docusign receives a total of 308 weekly downloads. As such, react-native-docusign popularity was classified as not popular.
We found that react-native-docusign demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
Socket found 37 malicious PyPI wheels that abuse Python startup hooks to launch a Bun-powered credential stealer tied to Mini Shai-Hulud/Miasma.

Security News
RubyGems and Bundler 4.0.13 introduced an opt-in cooldown feature that delays newly published gems during dependency resolution.

Security News
pnpm 11.5 now recognizes npm staged publish approvals in release metadata, preventing those releases from being mistaken for lower-trust package publishes.