top of page

Creating Webhooks (v2) in Box-Salesforce Integration

  • Writer: Oliver Jones
    Oliver Jones
  • Oct 15, 2021
  • 6 min read

Updated: Oct 19, 2021

Introduction


Webhooks are acknowledgements that are generated from Box during various events. We can use this to automate flows in Salesforce. Generally, a webhook can communicate through callback end points which is captured in Salesforce via Apex Rest.

For example, let’s assume that a user uploads a file in a specific folder which is connected to a Salesforce Case record, using this file upload event (FILE.UPLOADED), then we can use webhooks to receive the acknowledgement that the file has been uploaded now we can update the Case status to “Closed” and add comment as “File has been uploaded”.


Why we need a Webhook?


By default, Box provides a feature called Box Relay which supports some basic events like file/folder created, updated, deleted, etc. But Box Relay does not provide connections outside of Box i.e., to Salesforce and does not support some advanced events. By using webhooks we can overcome these cons.


List of Webhook events


The below is the list of events supported in Webhooks v2, that indicates every event’s name which we will be used as Identifier, when will it be triggered, whether it is supported by file or folder or both.

Event

Triggered

File?

Folder?

COLLABORATION.CREATED

A collaboration is created

No

Yes

COLLABORATION.ACCEPTED

A collaboration has been accepted

No

Yes

COLLABORATION.REJECTED

A collaboration has been rejected

No

Yes

COLLABORATION.REMOVED

A collaboration has been removed

No

Yes

COLLABORATION.UPDATED

A collaboration has been updated.

No

Yes

COMMENT.CREATED

A comment object is created

Yes

Yes

COMMENT.UPDATED

A comment object is edited

Yes

Yes

COMMENT.DELETED

A comment object is removed

Yes

Yes

​FILE.UPLOADED

A file is uploaded to or moved to this folder

No

Yes

FILE.PREVIEWED

A file is previewed

Yes

Yes

FILE.DOWNLOADED

A file is downloaded

Yes

Yes

FILE.TRASHED

A file is moved to the trash

Yes

Yes

FILE.DELETED

A file is permanently deleted

Yes

Yes

FILE.RESTORED

A file is restored from the trash

Yes

Yes

FILE.COPIED

A file is copied

Yes

Yes

FILE.MOVED

A file is moved from one folder to another

Yes

Yes

FILE.LOCKED

A file is locked

Yes

Yes

FILE.UNLOCKED

A file is unlocked

Yes

Yes

FILE.RENAMED

A file was renamed.

Yes

Yes

FOLDER.CREATED

A folder is created

No

Yes

FOLDER.RENAMED

A folder was renamed.

No

Yes

FOLDER.DOWNLOADED

A folder is downloaded

No

Yes

FOLDER.RESTORED

A folder is restored from the trash

No

Yes

FOLDER.DELETED

A folder is permanently removed

No

Yes

Event

Triggered

File?

Folder?

FOLDER.COPIED

A copy of a folder is made

No

Yes

FOLDER.MOVED

A folder is moved to a different folder

No

Yes

FOLDER.TRASHED

A folder is moved to the trash

No

Yes

METADATA_INSTANCE.CREATED

A new metadata template instance is associated with a file or folder

Yes

Yes

METADATA_INSTANCE.UPDATED

An attribute (value) is updated/deleted for an existing metadata template instance associated with a file or folder

Yes

Yes

METADATA_INSTANCE.DELETED

An existing metadata template instance associated with a file or folder is deleted

Yes

Yes

SHARED_LINK.DELETED

A shared link was deleted

Yes

Yes

SHARED_LINK.CREATED

A shared link was created

Yes

Yes

SHARED_LINK.UPDATED

A shared link was updated

Yes

Yes

TASK_ASSIGNMENT.CREATED

A task is created

Yes

Yes

TASK_ASSIGNMENT.UPDATED

A task assignment is changed

Yes

Yes

WEBHOOK.DELETED

When a webhook is deleted

No

No


Use Case


Consider the following use case,

  1. Insurance - A custom Object record will create a Box folder every time a record is created

  2. Users uploads files (docx) in Salesforce which will automatically transferred to Box

  3. Users collaborate with others and modify the files in the Box, and once ready, they will convert to PDFs

  4. Users use Meta Data in Box to indicate a file is ready to be picked up Salesforce

  5. Developer creates webhooks using the event METADATA_INSTANCE.UPDATED and transfers the final PDF back to Salesforce from Box


Setup Metadata in Box


  1. Make sure you are logged in as Co Admin (Service Account)

  2. Go to your Box -> select Admin console -> Content -> Metadata -> Create New

  3. “Name your Template” is the metadata template name that we will use to identify in the webhook, so give it a proper name.

  4. Set status to Visible

  5. Name your attribute as “Status”

  6. Choose Format as “Dropdown-Single Select”

  7. Add options as “New”, “In Progress” and “Save to SF”

  8. Click on Save.



Setting up Server Authentication and Custom App with Client Id and Secret



  1. Make sure you logged in as Co Admin (Service Account), and if you can find Dev console in your homepage, click on it otherwise follow the steps below,

  2. Go to your Box -> select Admin console -> select Apps -> go to Custom Apps -> Click on “Developer Website” as highlighted in this image below,

  3. Once you are in the Dev console, click on the “My Apps” and create a new App, choose custom app and choose authentication method as “Server Authentication (Client Credentials Grant)”.

  4. Give a name to the app and click on create app.

  5. Once the app is created, go to configuration tab to get client Id and secret. If you can’t get client secret, you will need to enable 2 factor Authentication. Go to your Box account Settings page or go to this page https://app.box.com/account/developer to enable 2 factor Authentication.

  6. You will need a Google Authenticator App and scan the QR code to authenticate the first time you login here after (only the developer). Now go back to configuration tab and get the Client Id and Secret.

  7. Make note of the “User Id” or “Enterprise Id” in the “General Settings” tab in the custom app. This will be needed to set up the webhook.




Setting up a Webhook URL in Salesforce using Apex REST webservice


Create an Apex class as a REST service with URL Mapping of '/BoxRestAPI/V1/*' and add this class to a public site in Salesforce.


@RestResource(urlMapping='/BoxRestAPI/V1/*')
global class BoxREST {
        @HttpPost
        global static void postItems() {
             RestRequest req = RestContext.request;
             RestResponse res = RestContext.response;
             try {
             //Request Body will be a Blob we need to encode 
             String responseJson = EncodingUtil.base64Encode(req.requestBody);
           Blob responseBlob = EncodingUtil.base64Decode(responseJson);
           String responseJson2 = responseBlob.toString();
           if(responseJson2 != null) {
                responseJson2 = responseJson2.replace('trigger', 'trigger_event');
                BoxPayload wrap = (BoxPayload)JSON.deserialize(responseJson2, BoxPayload.class);
                if(wrap.source.type == 'file') {
                     //Get Access Token
                     String accessToken = getAccessToken();
                     String parentFolderId = wrap.source.parent.id;
                     String fileId = wrap.source.id;
                     String metadataTemplateName = 'ReviewProcess';
                     String endPoint = 'https://api.box.com/2.0/files/' + fileId + '/metadata/enterprise/' + metadataTemplateName;
                     HttpResponse response = doCallout(endPoint, '', 'GET', accessToken);
                     Map<String,String> resp = (Map<String,String>)json.deserialize(response.getBody(), Map<String,String>.class);
                     //Read File only if the status is updated to Save to SF
                     if(resp.get('status') == 'Save to SF') {
                          BoxHandler.readFileContentFromBoxAsync(wrap.source.name, parentFolderId, fileId);
                     }
                }
           }
           }
           catch(Exception e) {
                System.debug('Error while calling REST server '+e.getMessage());
           }
      }
}

Create Box folder upon Record insert


We use Apex Trigger to create Box Folders upon Insert. The important thing to note here is that while creating a Box folder we need to configure the folder to support Webhook in the root folder as well as in its sub folders (cascading). Also, we need to configure which metadata to apply to the folder here.


trigger Insurance_Trigger_AT on Insurance__c (after insert) {
        //Check only one record is being inserted
        if(Trigger.New.size() == 1) {
             try {
             Insurance__c insRec = Trigger.New[0];
             box.Toolkit boxKit = new box.Toolkit();
             //First create object folder, if its already there returns the folder Id
             String objectFolderId = boxKit.createObjectFolderForRecordId(insRec.Id);
             if(String.isNotBlank(objectFolderId)) {
                //Create a Folder in Box within the parent folder Id
                String folderId = boxKit.createFolder(insRec.Name, objectFolderId, null);
                if(String.isNotBlank(folderId)) {
                     //Associate the SF record with its Box folder Id
                     box__FRUP__c assc = boxKit.createFolderAssociation(insRec.Id, folderId);
                     //Calling method to configure Metadata and Webhook to the fold                     BoxHandler.configureMetadata(folderId);
                     BoxHandler.configureWebhook(folderId);
                     boxKit.commitChanges();
                     Insurance__c insUpdate = new Insurance__c(Id = insRec.Id);
                     insUpdate.Box_Folder_Id__c = folderId;
                     update insUpdate;
                }
                else {
                     boxKit.commitChanges();
                     System.debug(''+boxKit.mostRecentError);
                }
           }
           else {
                boxKit.commitChanges();
                System.debug(''+boxKit.mostRecentError);
           }
           }
           catch(Exception e) {
                System.debug('Error while calling Box server '+e.getMessage());
           }
      }
}


The BoxHandler Class has the following methods,


Get Access Token:


//Get the Current Access Token
public static String getAccessToken() {
        Map<string,string> requestBody = new Map<string,string>();
        requestBody.put('client_id', <Client_ID>);
        requestBody.put('client_secret', <Client_SECRET>);
        requestBody.put('grant_type','client_credentials');
        requestBody.put('box_subject_type', <USER_OR_ENTERPRISE>);
        requestBody.put('box_subject_id', <USER_ID_OR_ENTERPRISE_ID>);
        String endPoint = 'https://api.box.com/oauth2/token';
      HttpResponse response = doCallout(endPoint, JSON.serialize(requestBody), 'POST', '');
}

Apply Metadata and Metadata Cascading to Box folder:


//Method that setup Metadata and Metadata Cascading
public static void configureMetadata(String folderId) {
        //Get Access Token to proceed for Metadata and Webhook
        String accessToken = getAccessToken();        
        //Setting up Metadata in the folder
        JSONGenerator jsGen = JSON.createGenerator(true);
        jsGen.writeStartObject();
        jsGen.writeStringField('status', 'New');
      jsGen.writeEndObject();
      //The Metadata Template name we created earlier
      String metadataTemplateName = 'ReviewProcess';
      String endPoint2 = 'https://api.box.com/2.0/folders/'+ folderId+'/metadata/enterprise/'+metadataTemplateName+'/';
      HttpResponse response = doCallout(endPoint2, jsGen.getAsString(), 'POST', accessToken);
      
      //Setting up Metadata Cascading in the folder
      Map<String, Object> metadata = new Map<String, Object>{
           'folder_id' => folderId,
           'scope' => 'enterprise',
           'templateKey' => metadataTemplateName
      };
      //The Metadata Template name we created earli      String metadataTemplateName = 'ReviewProcess';
      String endPoint3 = 'https://api.box.com/2.0/metadata_cascade_policies/';
      HttpResponse response = doCallout(endPoint3, JSON.serialize(metadata), 'POST', accessToken);
}

Apply Webhook v2 to a Box folder:


//Method that setup Webhook
public static void configureWebhook(String folderId) {
        //Get Access Token to proceed for Metadata and Webhook
        String accessToken = getAccessToken();
        JSONGenerator jsGen = JSON.createGenerator(true);
        jsGen.writeStartObject();
        //Webhook URL is your Site Org URL where you added your REST class + Webhook URL Mapping in the REST class
        jsGen.writeStringField('address', 'https://demodev-mstaz-mysite.cs33.force.com /services/apexrest/BoxRestAPI/V1');
        List<String> webhook_events = METADATA_INSTANCE.UPDATED.split(',|;')
      jsGen.writeStringField('triggers', webhook_events
      jsGen.writeFieldName('target');
      jsGen.writeStartObject();
      jsGen.writeStringField('id', folderId);
      jsGen.writeStringField('type', 'folder');
      jsGen.writeEndObject();
      jsGen.writeEndObject();
      String endPoint = 'https://api.box.com/2.0/webhooks/';
      HttpResponse response = doCallout(endPoint, jsGen.getAsString(), 'POST', accessToken);
}


Sending uploaded files (docx) from Salesforce to Box


//We use the Apex trigger on Attachment to send the file to Box
trigger Attachment_Trigger_AT on Attachment (after insert) {
        //Check only one record is being inserted
        if(Trigger.New.size() == 1) {
             Attachment attach = Trigger.New[0];
             String parentName = attach.parentId.getSObjectType().getDescribe().getName();
             //Check if this is the right attachment from our object
             if(parentName == 'Insurance__c' && attach.name.endsWithIgnoreCase('.docx')) {
                  Insurance__c insRec = [SELECT Id,Box_Folder_Id__c FROM Insurance__c WHERE Id =: attach.parentId];
                  if(String.isNotBlank(insRec.Box_Folder_Id__c)) {
                    box.Toolkit boxKit = new box.Toolkit();
                    //Send the attachment to Box
                    //No need to specify the folder Id here if the parent record is already associated with box folder Id properly, Box folder Id is optional here
		    String fileId = boxKit.createFileFromAttachment(attach, null, insRec.Box_Folder_Id__c, null);
                    boxKit.commitChanges();
                    if(String.isBlank(fileId)) {
                         System.debug(''+boxKit.mostRecentError);
                    }
                }
           }
      }
}

Download File from Box to Salesforce Attachment:


//Method that reads file from Box and saves as Attachment in SF
public static void readFileContentFromBoxAsync(String fileName, String parentFolderId, String fileId) {
        Box.Toolkit boxKit = new Box.Toolkit();
        //Will work only if the SF record is properly associated with a Box folder
        Id recId = boxKit.getRecordIdByFolderId(parentFolderId);
        boxKit.commitChanges();
        String endPoint = 'https://api.box.com/2.0/files/'+fileId+'/content/';
        HttpResponse response = doCallout(endPoint, '', 'GET', accessToken);
        String fileLocation = String.isNotEmpty(response.getHeader('location'))
                             ? response.getHeader('location')
                             : response.getHeader('Location');
      HttpRequest req = new HttpRequest();
      //Replace any spaces in fileLocation with %20
      String extFileUrl = fileLocation.replace(' ', '%20');
      //Set the end point URL
      req.setEndpoint(extFileUrl);
      req.setMethod('GET');
      req.setHeader('Content-Type', 'application/pdf');
      req.setCompressed(true);
      req.setTimeout(60000);
      HttpResponse res = new Http().send(req);
      Blob fileBlob = res.getBodyAsBlob();
      insert new Attachment(Name = fileName, body = fileBlob, contentType = 'application/pdf', parentId = recId);
}

Common Callout Method


//Method that reads file from Box and saves as Attachment in SF
public static HttpResponse doCallout(String endpoint, String reqBody, String method, String accessToken) {
        HttpRequest request = new HttpRequest();
        request.setMethod(method);
        request.setEndpoint(endpoint);
        if(String.isNotBlank(reqBody)  
           request.setBody(reqBody);
        if(String.isNotBlank(accessToken))
             request.setHeader('Authorization','Bearer ' + access_token);
      request.setHeader('content-type', 'application/json');
      return new Http().send(request);
}

Conclusion:


By using Webhooks v2, we can automate several other processes that Box provides as listed in the webhook events above and supports more features that the v1 webhooks.




Reference links:

 
 
 

Comentários


bottom of page