Managing documentation for sales opportunities in Salesforce can quickly become inefficient when done manually—especially when each opportunity type requires a unique folder structure. Sales teams often spend time creating folders and files in Azure Storage, risking inconsistency, delay, and errors in documentation.
This blog outlines why and how we implemented an automated integration between Salesforce and Azure Storage, enabling the system to auto-create folders based on opportunity record types.
We are handling various types of opportunities within Salesforce, each requiring a specific document structure to support sales processes and documentation workflows. The challenge we faced was efficiently managing and organizing related files in Azure Storage based on the type of opportunity created in Salesforce.
To address this, we implemented a seamless integration between Salesforce and Azure Storage. This integration enables automatic folder creation in Azure whenever a new opportunity is created in Salesforce. The system dynamically checks the record type of the opportunity and based on that, selects the appropriate folder structure to be created in Azure.
Once the folder structure is established, the integration proceeds with file creation. If the corresponding folder doesn't already exist in Azure Storage, it is created first
By automating this process, we aimed to reduce manual effort, eliminate errors, and ensure consistent organization of sales-related documentation across platforms. This integration not only improves operational efficiency but also strengthens our overall document management and compliance processes.
Objectives
Automate document management by creating opportunity-specific folders in Azure Storage without manual intervention.
Ensure consistency and accuracy in file organization based on the type of Salesforce opportunity.
Improve operational efficiency by reducing repetitive tasks for sales and support teams.
Enable centralized access to opportunity-related documents within Azure Storage for internal stakeholders.
Support compliance and auditability with structured, traceable document storage aligned to sales records.
Strategy
Identify Record Type on Opportunity Creation
When a new opportunity is created in Salesforce, we determine its record type to drive folder structure logic.
Dynamic Folder Structure in Azure
Based on the opportunity's record type, the integration dynamically creates the appropriate folder structure within Azure Storage. If a folder already exists, it’s reused.
End-to-End Automation
The entire process—from detecting the new opportunity to creating and populating files in Azure—is fully automated, ensuring speed, accuracy, and reliability.
To enable seamless folder creation in Azure Storage from Salesforce, we developed a custom integration leveraging APIs, authentication mechanisms, and middleware logic. Below is a detailed breakdown of the technologies used and the key implementation steps.
Technology Stack
Azure Storage REST API – For folder creation in Azure.
Remote Site Settings – To integrate Azure Storage with Salesforce, when making outbound calls from Apex
OAuth 2.0 – For secure Salesforce API authentication via remote site setting.
Azure Shared Key Authentication – Used for accessing Azure Storage securely.
Custom Meta Data – Used to securely store authentication credentials and other sensitive configuration details.
Key Implementation Steps
Event Trigger from Salesforce
A trigger on Opportunity creation invokes an Apex class make a secured callout to AZURE Blob storage via a secured HTTP request.
The Opportunity's record type is included in the payload to determine the folder structure.
Folder Creation in Azure
The middleware constructs a PUT request to Azure Storage to create a folder:
HTTP Method: PUT
Endpoint: https://<Storage_Account_Name>.file.core.windows.net/
<Folder_Path>/<Folder_Name>?restype=directory
Header:
x-ms-version: 2024-11-04
x-ms-date: Mon, 27 Jan 2014 22:50:32 GMT
x-ms-meta-Category: Images
Authorization: SharedKey <Storage_Account_Name>:<Storage_Account_Key>
Data Mapping
The middleware maps opportunity fields (like name, ID, type) to the corresponding folder path and file naming convention.
Error Handling & Logging
Implemented structured error responses with retry logic and logging for any failures (e.g., folder already exists, invalid keys, timeout).
Security & Data Privacy
OAuth 2.0 ensures secure communication with Salesforce.
Azure Storage access is protected via Shared Key authentication.
Sensitive data (keys, tokens) is stored securely using environment variables and encrypted storage mechanisms.
All communications are encrypted over HTTPS.
First, we created a trigger on the Opportunity object that calls the createAzureFolderStructure method upon record insertion.
public with sharing class OpportunityTriggerHandler extends TriggerHandler {
public override void afterInsert(){
OpportunityService.createAzureFolderStructure(Trigger.new);
}
}
The createAzureFolderStructure method accepts the folder name and folder path, then calls the createFolder method from the AzureRestService class. If an error occurs, it will log the error by creating a record in the Integration_Error_Log__c object.
public with sharing class OpportunityService {
public static void createAzureFolderStructure(List<Opportunity> newOppList){
String folderName = newOppList[0].Name;
String folderPath = 'Salesforce/2025/';
Integration_Error_Log__c folderCreationError = null;
folderCreationError = AzureRestService.createFolder(folderPath, folderName);
if(folderCreationError != null){
insert folderCreationError;
}
}
}
The method below creates an Opportunity folder in Azure Blob Storage.
public with sharing class AzureRestService {
private static final String METHOD_PUT = 'PUT';
private static final String SHARED_KEY = 'SharedKey ';
private static final String STORAGE_ACCOUNT_NAME = 'ABC01';
private static final String BLOB_STORAGE = 'salesforce';
private static final String IMAGES = 'Images';
private static final String X_MS_DATE = 'x-ms-date';
private static final String X_MS_VERSION_DATE = '2024-11-04';
private static final String X_MS_VERSION = 'x-ms-version';
private static final String X_MS_CATEGORY = 'x-ms-meta-category';
private static final String FOLDEREXTENDERSTRING = 'restype:directory';
private static final String FOLDEREXTENDERURL = '?restype=directory';
private static final String BASE_URL = '.file.core.windows.net/';
private static final String HTTPS = 'https://';
public static Integration_Error_Log__c createFolder(String folderPath, String folderName) {
String xmsdate = DateTime.now().format('E, dd MMM yyyy HH:mm:ss z', 'GMT');
String authToken = SHARED_KEY
+ STORAGE_ACCOUNT_NAME
+ ':'
+ generateSignature(folderPath, folderName, xmsdate);
String endPointUrl = HTTPS
+ STORAGE_ACCOUNT_NAME
+ BASE_URL
+ BLOB_STORAGE
+ (String.isEmpty(folderPath? '/' : '/' + folderPath+ '/')
+ folderName
+ FOLDEREXTENDERURL;
HttpResponse response = sendHttpRequest(endPointUrl, xmsdate, authToken);
return response;
}
}
The method below generates a signature for the API callout.
public static String generateSignature(String folderPath, String folderName, String xmsdate) {
String resourcePath = '/'
+ STORAGE_ACCOUNT_NAME
+ '/'
+ BLOB_STORAGE
+ (String.isEmpty(folderPath) ? '/' : '/' + folderPath+ '/')
+ folderName;
String stringToGenerateSignature = METHOD_PUT
+ '
' + '
' + '
' + '
' + '
' + '
'
+ '
' + '
' + '
' + '
' + '
' + '
'
+ X_MS_DATE + ':' + xmsdate + '
'
+ X_MS_CATEGORY + ':' + IMAGES + '
'
+ X_MS_VERSION + ':' + X_MS_VERSION_DATE + '
'
+ resourcePath
+ FOLDEREXTENDERSTRING ;
try {
Blob keyBytes = EncodingUtil.base64Decode(authDetails.Storage_Account_Key__c);
Blob signature = Crypto.generateMac(CRYPTO_TYPE_SHA, Blob.valueOf(stringToGenerateSignature), keyBytes);
return EncodingUtil.base64Encode(signature);
} catch (Exception e) {
throw new AuraHandledException('Failed to generate the signature.');
}
}
The method below makes an API callout to the Azure endpoint to create a folder.
public static HttpResponse sendHttpRequest(String endPointUrl, String xmsdate, String authToken) {
HttpRequest httpClientRequest = new HttpRequest();
httpClientRequest.setMethod(METHOD_PUT);
httpClientRequest.setEndPoint(endPointUrl);
httpClientRequest.setHeader(X_MS_VERSION, X_MS_VERSION_DATE);
httpClientRequest.setHeader(X_MS_DATE, xmsdate);
httpClientRequest.setHeader(AUTHORIZATION, authToken);
// Send the HTTP request and capture the response
Http httpRequest = new Http();
return httpRequest.send(httpClientRequest);
}
Get in touch with our expert Salesforce consultants to streamline your business processes and maximize efficiency.