Secure Exchanges SDK for PHP - Secure messaging, file exchange, and electronic signatures
Requires
- php: >=8.2
- ext-curl: *
- ext-json: *
- ext-mbstring: *
- ext-openssl: *
- chrome-php/chrome: ^1.0
- giggsey/libphonenumber-for-php: ^8.13
- paragonie/pqcrypto_compat: ^0.3
- phpseclib/phpseclib: ^3.0
- setasign/fpdf: ^1.8
- setasign/fpdi: ^2.6
- smalot/pdfparser: ^2.0
Requires (Dev)
- phpstan/phpstan: ^1.0
- phpunit/phpunit: ^10.0
- squizlabs/php_codesniffer: ^3.0
Suggests
- ext-opcache: Significantly speeds up the pure-PHP post-quantum fallback when ext-pqcrypto is not available (enable JIT).
- ext-pqcrypto: Native C implementation of ML-KEM/ML-DSA (github.com/paragonie/ext-pqcrypto). Strongly recommended in production: faster and side-channel resistant. Compat uses it automatically when loaded.
This package is auto-updated.
Last update: 2026-08-12 17:50:11 UTC
README
Version française : README.fr.md
Secure Exchanges PHP SDK
PHP SDK for the Secure Exchanges API — end-to-end encrypted messaging, secure file transfer, electronic signature, and PKI certification of PDF documents.
With this SDK, from your PHP application you can:
- send end-to-end encrypted messages and files, with or without a password, with SMS-based 2FA verification;
- have PDF documents signed electronically (signature zones, initials, dates, text fields, and more);
- certify PDFs with a digital certificate (PKI) before sending them for signature;
- read, extend, and delete messages, and create secure reply envelopes;
- view activity logs (sends, opens, signatures).
Table of contents
- Security
- Installation
- Requirements
- Credentials
- Configuration
- SDK architecture
- Usage guide
- API reference
- Support
- License
Security
- End-to-end encryption: the content (subject, message body, files) is encrypted inside your application before it leaves your server, using keys generated locally and never transmitted to the Secure Exchanges server — the server only ever sees encrypted data.
- Post-quantum key exchange: ML-KEM-1024 (FIPS 203), resistant to quantum-computer attacks, signed with ML-DSA-87 (FIPS 204).
- AES-256 encryption of the content and of all exchanges with the server.
- Interception protection: any man-in-the-middle (MITM) attack attempt causes the connection to fail.
- Nothing to manage: the SDK automatically applies the entire protocol on every call — you never have to handle any cryptographic primitives yourself.
Installation
composer require secure-exchanges/sdk
Or from source:
cd phpSDK
composer install
Dependencies
Dependencies are managed by Composer (see composer.json):
paragonie/pqcrypto_compat(^0.3) — post-quantum cryptography (ML-KEM / ML-DSA)phpseclib/phpseclib— cryptography (RSA for some auxiliary functions, utilities)setasign/fpdi,setasign/fpdf— PDF manipulationsmalot/pdfparser— PDF readingchrome-php/chrome— PKI certification (headless browser)giggsey/libphonenumber-for-php— phone number validation
Requirements
To use the SDK, you need:
- PHP 8.2 or newer (
composer installfails on PHP 8.1) - PHP extensions:
openssl,json,mbstring,curl ext-sockets— required for PKI signing (used bychrome-php)- Google Chrome or Chromium installed on the machine — required only for PDF certification (PKI); the binary path can be provided via the
SE_CHROME_PATHenvironment variable - A serial number (
serial) — the UUID of your license - An API user ID (
api_user) — the UUID of the API user - An API password (
api_password) — the UUID of the API password
Recommended in production: the native ext-pqcrypto extension (github.com/paragonie/ext-pqcrypto) — significantly faster for post-quantum cryptography than the pure-PHP implementation installed by Composer. No code changes are needed: the SDK automatically switches to the extension as soon as it is loaded. Otherwise, enabling ext-opcache (with JIT) noticeably speeds up the pure-PHP implementation.
Credentials
Where to find your credentials
The 3 required credentials (serial, api_user, api_password) are UUIDs (GUIDs) that you obtain from your Secure Exchanges administration portal when creating your API license.
| Credential | Description |
|---|---|
serial |
Serial number of your license |
api_user |
API user ID |
api_password |
API user password |
Configuration
Configuration via .env
The SDK is configured through a .env file at the root of your project (add it to .gitignore — never commit it):
# .env SE_SERIAL=d17ca972-5f70-4961-bac4-98e6b91b1dc4 SE_API_USER=0fa0cf77-4ca5-4406-8623-1ed0ffd47290 SE_API_PASSWORD=4bfeabdf-c498-448a-9255-1e17c3ef8c0b SE_USE_PREVIEW=false # true = test (preview) environment; production by default SE_VERIFY_SSL=true # keep true in both preview and production SE_CULTURE=fr-CA SE_CHROME_PATH= # optional: Chrome/Chromium path (PKI certification)
SE_USE_PREVIEW=truepoints the SDK to the preview test environment; your preview and production credentials are different — a production serial will not work in preview, and vice versa.
SettingsHelper::configureFromEnv() does everything in one call: it loads the .env file (looked up in the current directory, then next to the main script, without ever overwriting environment variables that are already set), selects the environment, and reads the credentials:
use SecureExchanges\SDK\Client\SecureExchangesClient; use SecureExchanges\SDK\Client\SEMSClient; use SecureExchanges\SDK\Helpers\SettingsHelper; // Returns ['apiEndpoint' => ..., 'semsEndpoint' => ..., // 'environment' => 'production'|'preview'] $endpoints = SettingsHelper::configureFromEnv(); $client = new SecureExchangesClient($endpoints['apiEndpoint']); $sems = new SEMSClient($endpoints['semsEndpoint']); // Credentials are available through the getters: $serial = SettingsHelper::getSerial(); $apiUser = SettingsHelper::getApiUser(); $apiPassword = SettingsHelper::getApiPassword(); $culture = SettingsHelper::getCulture(); // e.g. "fr-CA" $verifySsl = SettingsHelper::getVerifySsl();
You can also pass an explicit path: SettingsHelper::configureFromEnv('/path/to/.env').
SDK architecture
The SDK is organized into three main modules (namespace SecureExchanges\SDK, PSR-4 from src/):
Client/— the HTTP layer:SecureExchangesClient(main API),SEMSClient(email and key delivery service), both built onBaseClient.Helpers/— the business logic:MessageHelper(send/read/delete),HandShakeHelper(post-quantum handshake),CryptoHelper(AES/RSA/SHA primitives),SignHelperandZoneBuilder(signature zones),PkiFileHelper(PDF certification),FileHelper(encrypted upload/download),SettingsHelper(configuration), plus validation helpers (EmailHelper,PhoneHelper), logging (LogsHelper), and licensing (LicenceHelper).Models/— the typed objects: responses (Answer/), entities (Entity/), enumerations (Enum/), transport objects (Transport/), JSON models (JsonModels/), arguments (Args/). TheCallback/folder contains the notification (callback) objects.
Usage guide
All the examples below assume the following setup:
use SecureExchanges\SDK\Client\SecureExchangesClient; use SecureExchanges\SDK\Client\SEMSClient; use SecureExchanges\SDK\Helpers\SettingsHelper; use SecureExchanges\SDK\Helpers\MessageHelper; use SecureExchanges\SDK\Models\Entity\RecipientInfo; $endpoints = SettingsHelper::configureFromEnv(); $client = new SecureExchangesClient($endpoints['apiEndpoint']); $sems = new SEMSClient($endpoints['semsEndpoint']); $serial = SettingsHelper::getSerial(); $apiUser = SettingsHelper::getApiUser(); $apiPassword = SettingsHelper::getApiPassword();
Sending a simple message
$answer = MessageHelper::multiRecipientMessage( apiClient: $client, serial: $serial, apiUser: $apiUser, apiPassword: $apiPassword, recipients: [new RecipientInfo(email: 'recipient@example.com')], message: 'Hello, here is your secure message.', subject: 'Confidential message', cultureId: 'en-US', semsClient: $sems ); if ($answer->status === 200 && $answer->recipientsAnswer) { echo 'URL: ' . $answer->recipientsAnswer[0]->answer->url; }
Sending with a password and SMS 2FA
The password and the delivery channel are two independent, combinable options. The channel is controlled by the SendMethodEnum enumeration:
| Value | Behavior |
|---|---|
SendMethodEnum::ONLY_EMAIL (default) |
Link sent by email |
SendMethodEnum::SMS_ONLY |
Link sent by SMS only |
SendMethodEnum::SMS_WITH_EMAIL_CODE |
Link by SMS, opening code by email |
SendMethodEnum::EMAIL_WITH_SMS_CODE |
Link by email, opening code by SMS (2FA) |
use SecureExchanges\SDK\Models\Enum\SendMethodEnum; $answer = MessageHelper::multiRecipientMessage( apiClient: $client, serial: $serial, apiUser: $apiUser, apiPassword: $apiPassword, recipients: [new RecipientInfo(email: 'user@example.com', phone: '+15145551234')], message: 'Confidential document attached.', subject: 'Protected document', password: 'SecretPassword123', sendMethod: SendMethodEnum::EMAIL_WITH_SMS_CODE, cultureId: 'en-US', maximumOpenTime: 3, // maximum number of opens minutesExpiration: 1440, // expiration in minutes (1440 = 24 h) semsClient: $sems );
Sending with attachments
// From disk $answer = MessageHelper::multiRecipientMessage( apiClient: $client, serial: $serial, apiUser: $apiUser, apiPassword: $apiPassword, recipients: [new RecipientInfo(email: 'recipient@example.com')], message: 'See attached files.', subject: 'Secure files', filesPath: ['/path/to/document.pdf', '/path/to/image.png'], semsClient: $sems ); // From memory $answer = MessageHelper::multiRecipientMessage( apiClient: $client, serial: $serial, apiUser: $apiUser, apiPassword: $apiPassword, recipients: [new RecipientInfo(email: 'recipient@example.com')], message: 'Report and data attached.', subject: 'Monthly report', filesList: [ ['binary' => $pdfBytes, 'file_name' => 'report.pdf'], ['binary' => $csvBytes, 'file_name' => 'data.csv'], ], semsClient: $sems );
Both parameters (filesPath and filesList) can be combined in the same send.
Sending with PKI certification
PKI certification applies a digital certificate to the PDF before it is sent for signature. It requires Chrome/Chromium (headless browser via chrome-php) and the ext-sockets extension. Signature zones are defined with ZoneBuilder, which reads the actual dimensions of the PDF pages. The flow:
use SecureExchanges\SDK\Helpers\CryptoHelper; use SecureExchanges\SDK\Helpers\ZoneBuilder; use SecureExchanges\SDK\Helpers\SignHelper; use SecureExchanges\SDK\Helpers\PkiFileHelper; use SecureExchanges\SDK\Models\Args\CertifyPdfArgs; use SecureExchanges\SDK\Models\Args\FileArgs; use SecureExchanges\SDK\Models\Entity\SignZoneDefinition; use SecureExchanges\SDK\Models\Transport\FileZoneDefinition; use SecureExchanges\SDK\Models\Transport\RecipientZoneDefinition; // 1. Read the PDF and compute its SHA512 $pdfPath = '/path/to/contract.pdf'; $pdfBytes = file_get_contents($pdfPath); $sha512 = CryptoHelper::getSha512HashOfBytes($pdfBytes); // 2. Define the signature zones with ZoneBuilder $zoneBuilder = new ZoneBuilder($pdfPath, isPki: true); $zones = [ // Signature zone on page 1 $zoneBuilder->createFieldSignZone( page: 1, fieldName: 'signature1', x: 60, y: 400, width: 200, height: 50 ), // Date zone filled in automatically at signing time $zoneBuilder->createFieldDateSignZone( page: 1, fieldName: 'date1', x: 60, y: 500, width: 100, height: 25, fontSize: 12 ), ]; $zoneBuilder->close(); // 3. Bind the zones to the recipient (file referenced by its SHA512) $recipientZones = [ new RecipientZoneDefinition( email: 'signer@example.com', phone: null, zonesDefByFile: [ new FileZoneDefinition( uniqueName: $sha512, zonesDef: new SignZoneDefinition(mode: 'define', zones: $zones), doNotEncryptPDF: false, doNotAppendCertificateToFile: false, isPKIFile: true ) ] ) ]; // 4. Get the certification token $tokenResponse = SignHelper::getCertificationToken( apiClient: $client, serial: $serial, apiUser: $apiUser, apiPassword: $apiPassword, originalHash: $sha512, cultureId: 'en-US', tokenType: 'CertifyDocument' ); $certData = $tokenResponse['certification_data']; // 5. Certify the PDF (headless browser) $certifyArgs = new CertifyPdfArgs( file: new FileArgs($pdfBytes, 'contract.pdf'), recipients: ['signer@example.com'], certifyData: $certData, detectExistingFields: true, recipientZoneDefinitions: $recipientZones ); $pkiHelper = new PkiFileHelper(isPreview: false, culture: 'en-US'); $certifiedPdf = $pkiHelper->certifyPkiPdf($certifyArgs); $pkiHelper->close(); // 6. Send the certified PDF $answer = MessageHelper::multiRecipientMessage( apiClient: $client, serial: $serial, apiUser: $apiUser, apiPassword: $apiPassword, recipients: [new RecipientInfo(email: 'signer@example.com')], message: 'Please sign this certified document.', subject: 'PKI document to sign', cultureId: 'en-US', certifiedPdfs: [$certifiedPdf], semsClient: $sems );
A complete, runnable example — including signature options (SignOptions: restricting the signing mode, requesting a file in return) and callback parameters (contextual keys) — can be found in example_pki_with_signature_options.php at the root of phpSDK/.
Retrieving a message
// Parse the received link $msgParams = MessageHelper::getSecureExchangesMessageFromLink($url); // Decrypt the message $response = MessageHelper::getMessage( apiClient: $client, serial: $serial, apiUser: $apiUser, apiPassword: $apiPassword, msgParams: $msgParams, password: null, // password if the message is protected digitCode: null, // SMS code if 2FA is enabled semsClient: $sems ); if ($response->status === 200) { echo "Subject: {$response->subject}\n"; echo "Message: {$response->message}\n"; // $response->filesMetaData, $response->remainingOpenTime, $response->expireOn, ... }
Note: each read consumes one of the message's allowed opens (
maximumOpenTime).
Deleting a message
$success = MessageHelper::deleteMessageByTrackingId( apiClient: $client, serial: $serial, apiUser: $apiUser, apiPassword: $apiPassword, trackingId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', cultureId: 'en-US' );
Reply envelopes
Envelopes let a recipient send documents to you securely:
// Simple envelope (single recipient) $envelope = MessageHelper::getEnveloppe( apiClient: $client, serial: $serial, apiUser: $apiUser, apiPassword: $apiPassword, subject: 'Send me your documents', destination: 'recipient@example.com', cultureId: 'en-US', replyExpirationHours: 48, maximumReplyOpenTime: 3, replyToApi: true, authorizedExtensions: ['.pdf', '.docx'] ); if ($envelope->url) { echo "Envelope URL: {$envelope->url}"; } // Multiple envelopes (one per recipient; a phone number on the // recipient automatically enables 2FA) $envelopes = MessageHelper::getEnvelopes( apiClient: $client, serial: $serial, apiUser: $apiUser, apiPassword: $apiPassword, subject: 'Required documents', recipients: [ ['Email' => 'user1@example.com'], ['Email' => 'user2@example.com', 'Phone' => '+15145551234'], ] );
API reference
MessageHelper
Static class for sending and receiving messages (src/Helpers/MessageHelper.php).
| Method | Description |
|---|---|
multiRecipientMessage(...) |
Sends a message to one or more recipients |
getMessage($apiClient, $serial, $apiUser, $apiPassword, $msgParams, $password = null, $digitCode = null, $semsClient = null) |
Retrieves and decrypts a message |
getSecureExchangesMessageFromLink($link) |
Parses a Secure Exchanges link into parameters for getMessage |
deleteMessageByTrackingId($apiClient, $serial, $apiUser, $apiPassword, $trackingId, $cultureId = 'en-US') |
Deletes a message |
getEnveloppe(...) |
Creates a reply envelope (single recipient) |
getEnvelopes(...) |
Creates multiple envelopes |
extendMessageLive($apiClient, $messageId, $extendMessageToken) |
Extends the lifetime of a message being read |
sendTrace($apiClient, $traceContent) |
Sends a support trace |
multiRecipientMessage parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
apiClient |
SecureExchangesClient |
required | API client |
serial |
string |
required | Serial number (UUID) |
apiUser |
string |
required | API user ID (UUID) |
apiPassword |
string |
required | API password (UUID) |
recipients |
RecipientInfo[] |
required | Recipients |
message |
string |
required | Message content (HTML accepted) |
subject |
string |
required | Subject |
password |
?string |
null |
Protection password |
filesList |
?array |
null |
In-memory files (['binary' => ..., 'file_name' => ...]) |
filesPath |
?array |
null |
Paths of files on disk |
sendMethod |
SendMethodEnum |
ONLY_EMAIL |
Delivery channel (see table above) |
getBackHtml |
bool |
true |
true = you send the email yourself, false = Secure Exchanges sends it |
showSubject |
bool |
true |
Show the subject in the notification |
getNotify |
bool |
true |
Notify when the message is opened |
cultureId |
string |
'en-US' |
Language ('fr-CA', 'en-US', …) |
maximumOpenTime |
int |
1 |
Maximum number of opens |
minutesExpiration |
int |
10080 |
Expiration in minutes (default: 7 days) |
createMessageCopy |
bool |
false |
Copy for the sender |
name |
string |
'Secure Exchanges' |
Sender name |
callbackParameters |
?string |
null |
Callback parameters (JSON) |
replyOptions |
?array |
null |
Reply options |
ownerDontNeedToSign |
bool |
true |
The sender does not sign |
semsClient |
?SEMSClient |
null |
SEMS client (created automatically if omitted) |
certifiedPdfs |
?array |
null |
PKI-certified PDFs (CertifiedPdfContainer) |
signOptions |
?SignOptions |
null |
Signature options (restriction, return upload) |
Returns: MultiRecipientAnswer (status, data, recipientsAnswer[] — each element contains a SendMessageAnswer with url, trackingId, htmlMsg, …).
SignHelper
Signature zone and certification management (src/Helpers/SignHelper.php).
| Method | Description |
|---|---|
getCertificationToken($apiClient, $serial, $apiUser, $apiPassword, $originalHash, $cultureId, $tokenType = null, $metadata = null) |
Obtains a PKI certification token |
addFileZoneDefToRecipientIndex(...) |
Converts templates into zone definitions |
convertRecipientIndexToList($recipientIndex) |
Converts the index into a list for multiRecipientMessage |
concatRecipientZoneDefinitionLists($list1, $list2) |
Merges two zone definition lists |
validateAllRecipientsHaveZones(...) |
Checks that all recipients have zones |
validateDocumentSignatureIntegrity(...) |
Validates the integrity of a signed document |
applyPKI($manifest, $certificationData) |
Applies the certification data to a manifest |
PkiFileHelper
PKI certification of PDFs via headless browser (src/Helpers/PkiFileHelper.php, based on chrome-php).
$helper = new PkiFileHelper(isPreview: false, culture: 'en-US'); $certified = $helper->certifyPkiPdf($args); // CertifyPdfArgs -> CertifiedPdfContainer $helper->close();
The Chrome/Chromium binary is detected automatically, or forced via the SE_CHROME_PATH environment variable.
CertifyPdfArgs: file (FileArgs: binary + name), recipients (emails), certifyData (certification token), detectExistingFields (default true), recipientZoneDefinitions, defineRecipients (optional callback).
CertifiedPdfContainer (return value): fileBytes (certified PDF), fileName, signFilesRequired (including the new sha512), recipientZoneDefinitions (zones updated with the new hash).
ZoneBuilder
Builds signature zones from a PDF (src/Helpers/ZoneBuilder.php).
$builder = new ZoneBuilder($filePath, isPki: true); $zone = $builder->createFieldSignZone(page: 1, fieldName: 'sig1', x: 50, y: 600, width: 200, height: 80); $builder->close();
Zone types: signature, initials, text, number, date, signing date, checkbox, radio buttons, text area, option list, dropdown list. Utilities: getPageCount(), getPageDimensions($page).
SettingsHelper
SDK configuration (src/Helpers/SettingsHelper.php).
| Method | Description |
|---|---|
configureFromEnv($envFile = null) |
Loads .env, selects the environment, reads the credentials |
loadEnv($envFile = null) |
Loads a .env file without overwriting the existing environment |
getSerial() / getApiUser() / getApiPassword() |
Configured credentials |
getLicenceEmail() / getCulture() / getVerifySsl() |
Configured options |
getSemsEndpoint() / getFileUploadHandler() / getFileDownloadHandler() |
Effective endpoints |
reset() |
Resets to default values |
Other helpers
| Class | Purpose |
|---|---|
LogsHelper |
getLog(...) (by messageId or trackingId), getLogs($apiClient, $serial, $apiUser, $apiPassword, $fromDate, $toDate, $userLogSerial = null) |
LicenceHelper |
getSerialInfo(...), isLicenceValid(...) — license information and validity |
CryptoHelper |
Primitives: getSha512HashOfBytes, getSha512OfFile, AES (encryptBinary/decryptBinaryFromBytes, …), RSA, .NET GUID (guidToBytesLe) |
EmailHelper / PhoneHelper |
Validation of email addresses and phone numbers (E.164, SMS capability) |
Data models
// Recipient — a phone number enables the SMS/2FA features new RecipientInfo(email: 'user@example.com', phone: '+15145551234');
GetMessageResponse (reading): status, subject, message, filesMetaData, remainingOpenTime, expireOn, createdDate, callbackParameters, containsSignsFiles, …
GetEnveloppeResponse (envelope): url, statusCode, repId, recipient.
Support
For any question or issue, contact supportdev@secure-exchanges.info or visit secure-exchanges.com. Product documentation: help.secure-exchanges.com.