Tailored REST API Endpoint For Salesforce

Many third-party applications offer their own APIs or webhooks, facilitating seamless and often simplified integrations across platforms. Another integration approach may involve using middleware or an enterprise service bus (ESB) to facilitate data movement. Regardless, the Salesforce REST API can handle standard CRUD operations (create, read, update, and delete), or you can develop custom Apex logic and expose it through the REST API to streamline processing.

Consider the scenario of integrating Stripe with Salesforce. Stripe excels in processing credit cards and managing subscription and payment details, while Salesforce effectively manages interactions between your business and customers. It makes sense to have Stripe notify Salesforce when a payment is made, keeping your team informed.

Before delving deeper, let’s introduce a key concept. A webhook is an HTTP callback triggered when a specific event occurs. An application implementing webhooks will POST a message to a URL upon such events. In this example, Stripe will send a JSON message to Salesforce, where Salesforce will consume the message and perform additional processing based on the JSON content.

If you have experience with web services development, you’re aware that the Salesforce REST API can handle various tasks without requiring custom Apex classes. The API enables querying records, inserting new records, updating records, deleting records, and more. To demonstrate the API’s capabilities and emphasize its power, we’ll create a custom Apex class for performing multiple operations.

Now, let’s delve into the example. For simplicity, let’s assume that the JSON posted to our Salesforce endpoint will be structured as follows:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
{
"id": "ch_abcdefghijklmnopqrstuvwx",
"amount": 9900,
"description": "greg@interactiveties.com",
"source": {
"id": "card_19mlu82GgpHzUJkgFLGaSS8e",
"brand": "American Express",
"exp_month": 11,
"exp_year": 2020,
"last4": "2222",
"name": "greg@interactiveties.com",
},
"status": "succeeded",
"type": "charge.succeeded"
}
{ "id": "ch_abcdefghijklmnopqrstuvwx", "amount": 9900, "description": "greg@interactiveties.com", "source": { "id": "card_19mlu82GgpHzUJkgFLGaSS8e", "brand": "American Express", "exp_month": 11, "exp_year": 2020, "last4": "2222", "name": "greg@interactiveties.com", }, "status": "succeeded", "type": "charge.succeeded" }
{
	"id": "ch_abcdefghijklmnopqrstuvwx", 
	"amount": 9900, 
	"description": "greg@interactiveties.com", 
	"source": { 
		"id": "card_19mlu82GgpHzUJkgFLGaSS8e", 
		"brand": "American Express", 
		"exp_month": 11, 
		"exp_year": 2020, 
		"last4": "2222", 
		"name": "greg@interactiveties.com", 
	}, 
	"status": "succeeded", 
	"type": "charge.succeeded" 
}

Our task is to transform this JSON request into an object usable within our Apex class. Subsequently, we will perform a query to locate a Contact with the email address specified in the value for the description, in this instance, “greg@interactiveties.com.” Once we identify the contact, we will utilize certain attributes from the JSON object to generate a corresponding Task record associated with the Contact obtained from our query.

As a response to the request, we will furnish the Task’s Id that we created along with a success message. The JSON we send in response will be formatted as follows:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
{
"status": "success",
"contactid": "0033000000YVLQWAA5",
"taskid": "00T3000000rEvJtEAK",
"message": ""
}
{ "status": "success", "contactid": "0033000000YVLQWAA5", "taskid": "00T3000000rEvJtEAK", "message": "" }
{
	"status": "success", 
	"contactid": "0033000000YVLQWAA5", 
	"taskid": "00T3000000rEvJtEAK", 
	"message": ""
}

Now that the objective is clear, let’s proceed to the Apex class:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
/*
Created by: Greg Hacic
Last Update: 17 February 2017 by Greg Hacic
Questions?: greg@ities.co
Notes:
- API endpoint accepts JSON similar to:
{
"id": "ch_abcdefghijklmnopqrstuvwx",
"amount": 9900,
"description": "greg@interactiveties.com",
"source": {
"id": "card_123456789123456789123456",
"brand": "American Express",
"exp_month": 11,
"exp_year": 2021,
"last4": "2222",
"name": "greg@interactiveties.com"
},
"status": "succeeded",
"type": "charge.succeeded"
}
- queries for the Contact with the email address provided in the description key/value pair from the JSON request
- creates a Task
- returns JSON similar to:
{
"status": "success",
"contactid": "0033000000YVLQWAA5",
"taskid": "00T3000000rEvJtEAK",
"message": ""
}
*/
//@RestResource annotation exposes the class as a REST resource
@RestResource(urlMapping='/demo/createTask/*') //endpoint definition > {Salesforce Base URL}/services/apexrest/demo/createTask/
global class createTask {
//primary logic for the class
@HttpPost //HttpPost annotation exposes the method as a REST resource and called when an HTTP POST request is sent
global static responseWrapper taskCreationLogic() {
RestRequest req = RestContext.request; //the RestRequest for the Apex REST method
responseWrapper responseJSON = new responseWrapper(); //responseWrapper object for API response
String typeOfCard = ''; //placeholder for the type of card string
String last4OfCard = ''; //placeholder for the last four digits of the card
String emailAddress = ''; //placeholder for an email address
Map<String, Object> body = new Map<String, Object>(); //placeholder for the JSON Body of the request
Map<String, Object> src = new Map<String, Object>(); //placeholder for the source object from the JSON request
String jsonBody = req.requestBody.toString(); //the body of the request
if (!String.isBlank(jsonBody)) { //if the request body is NOT white space, empty ('') or null
body = (Map<String, Object>)JSON.deserializeUntyped(jsonBody); //deserializes the JSON string into collections of primitive data types
if (body.containsKey('description')) { //if there is a key of description in our body map
emailAddress = (String)body.get('description'); //grab the value for the description key from the body map and cast it to a string
List<Contact> queriedContacts = [SELECT Id FROM Contact WHERE Email = :emailAddress ORDER BY CreatedDate DESC LIMIT 1]; //query for a Contact that has the email address
if (!queriedContacts.isEmpty()) { //if the list is not empty
if (body.containsKey('source')) { //if there is a key of source in our body map
src = (Map<String, Object>)body.get('source'); //grab the value for the source key from the body map and cast it to a new map (String > primative data types)
if (src.containsKey('brand')) { //if there is a key of brand in our src map
typeOfCard = (String)src.get('brand'); //grab the value for the brand key from the src map and cast it to a string
}
if (src.containsKey('last4')) { //if there is a key of last4 in our src map
last4OfCard = (String)src.get('last4'); //grab the value for the last4 key from the src map and cast it to a string
}
}
responseJSON.contactid = queriedContacts[0].Id; //populate the Id of the Contact record to our response object
Task newTask = new Task(ActivityDate = Date.Today(), Description = 'The '+typeOfCard+' credit card ending in '+last4OfCard+' was charged.', Status = 'Complete', Subject = typeOfCard+' Card Charged', WhoId = queriedContacts[0].Id); //create a Task
Database.SaveResult insertNewTask = Database.insert(newTask); //insert the new Task
if (!insertNewTask.isSuccess()) { //if the insert DML was NOT successful
List<Database.Error> errors = insertNewTask.getErrors(); //grab the error array from the SaveResult object
//respond with failure
responseJSON.status = 'failure';
responseJSON.message = errors[0].getMessage(); //set the message to the first error in the array
} else { //otherwise, the insert was successful
responseJSON.taskid = insertNewTask.getId(); //populate the Id of the Task record to our response object
}
} else { //otherwise, no key of source in our map
//respond with failure
responseJSON.status = 'failure';
responseJSON.message = 'There are no Contacts with the email address of '+emailAddress+'.';
}
} else { //otherwise, no key of description in our map
//respond with failure
responseJSON.status = 'failure';
responseJSON.message = 'No description in the JSON request.';
}
} else { //otherwise, the JSON body was white space, empty ('') or null
//respond with failure
responseJSON.status = 'failure';
responseJSON.message = 'Things basically broke...';
}
return responseJSON; //return the JSON response
}
//wrapper class for the response to an API request
global class responseWrapper {
global String status {get;set;} //status string
global String contactid {get;set;} //18 character Contact record Id
global String taskid {get;set;} //18 character Task record Id
global String message {get;set;} //message string
//constructor
global responseWrapper() {
//default all values
this.status = 'success';
this.contactid = '';
this.taskid = '';
this.message = '';
}
}
}
/* Created by: Greg Hacic Last Update: 17 February 2017 by Greg Hacic Questions?: greg@ities.co Notes: - API endpoint accepts JSON similar to: { "id": "ch_abcdefghijklmnopqrstuvwx", "amount": 9900, "description": "greg@interactiveties.com", "source": { "id": "card_123456789123456789123456", "brand": "American Express", "exp_month": 11, "exp_year": 2021, "last4": "2222", "name": "greg@interactiveties.com" }, "status": "succeeded", "type": "charge.succeeded" } - queries for the Contact with the email address provided in the description key/value pair from the JSON request - creates a Task - returns JSON similar to: { "status": "success", "contactid": "0033000000YVLQWAA5", "taskid": "00T3000000rEvJtEAK", "message": "" } */ //@RestResource annotation exposes the class as a REST resource @RestResource(urlMapping='/demo/createTask/*') //endpoint definition > {Salesforce Base URL}/services/apexrest/demo/createTask/ global class createTask { //primary logic for the class @HttpPost //HttpPost annotation exposes the method as a REST resource and called when an HTTP POST request is sent global static responseWrapper taskCreationLogic() { RestRequest req = RestContext.request; //the RestRequest for the Apex REST method responseWrapper responseJSON = new responseWrapper(); //responseWrapper object for API response String typeOfCard = ''; //placeholder for the type of card string String last4OfCard = ''; //placeholder for the last four digits of the card String emailAddress = ''; //placeholder for an email address Map<String, Object> body = new Map<String, Object>(); //placeholder for the JSON Body of the request Map<String, Object> src = new Map<String, Object>(); //placeholder for the source object from the JSON request String jsonBody = req.requestBody.toString(); //the body of the request if (!String.isBlank(jsonBody)) { //if the request body is NOT white space, empty ('') or null body = (Map<String, Object>)JSON.deserializeUntyped(jsonBody); //deserializes the JSON string into collections of primitive data types if (body.containsKey('description')) { //if there is a key of description in our body map emailAddress = (String)body.get('description'); //grab the value for the description key from the body map and cast it to a string List<Contact> queriedContacts = [SELECT Id FROM Contact WHERE Email = :emailAddress ORDER BY CreatedDate DESC LIMIT 1]; //query for a Contact that has the email address if (!queriedContacts.isEmpty()) { //if the list is not empty if (body.containsKey('source')) { //if there is a key of source in our body map src = (Map<String, Object>)body.get('source'); //grab the value for the source key from the body map and cast it to a new map (String > primative data types) if (src.containsKey('brand')) { //if there is a key of brand in our src map typeOfCard = (String)src.get('brand'); //grab the value for the brand key from the src map and cast it to a string } if (src.containsKey('last4')) { //if there is a key of last4 in our src map last4OfCard = (String)src.get('last4'); //grab the value for the last4 key from the src map and cast it to a string } } responseJSON.contactid = queriedContacts[0].Id; //populate the Id of the Contact record to our response object Task newTask = new Task(ActivityDate = Date.Today(), Description = 'The '+typeOfCard+' credit card ending in '+last4OfCard+' was charged.', Status = 'Complete', Subject = typeOfCard+' Card Charged', WhoId = queriedContacts[0].Id); //create a Task Database.SaveResult insertNewTask = Database.insert(newTask); //insert the new Task if (!insertNewTask.isSuccess()) { //if the insert DML was NOT successful List<Database.Error> errors = insertNewTask.getErrors(); //grab the error array from the SaveResult object //respond with failure responseJSON.status = 'failure'; responseJSON.message = errors[0].getMessage(); //set the message to the first error in the array } else { //otherwise, the insert was successful responseJSON.taskid = insertNewTask.getId(); //populate the Id of the Task record to our response object } } else { //otherwise, no key of source in our map //respond with failure responseJSON.status = 'failure'; responseJSON.message = 'There are no Contacts with the email address of '+emailAddress+'.'; } } else { //otherwise, no key of description in our map //respond with failure responseJSON.status = 'failure'; responseJSON.message = 'No description in the JSON request.'; } } else { //otherwise, the JSON body was white space, empty ('') or null //respond with failure responseJSON.status = 'failure'; responseJSON.message = 'Things basically broke...'; } return responseJSON; //return the JSON response } //wrapper class for the response to an API request global class responseWrapper { global String status {get;set;} //status string global String contactid {get;set;} //18 character Contact record Id global String taskid {get;set;} //18 character Task record Id global String message {get;set;} //message string //constructor global responseWrapper() { //default all values this.status = 'success'; this.contactid = ''; this.taskid = ''; this.message = ''; } } }
/*
	Created by: Greg Hacic
	Last Update: 17 February 2017 by Greg Hacic
	Questions?: greg@ities.co
	
	Notes:
		- API endpoint accepts JSON similar to:
			{
				"id": "ch_abcdefghijklmnopqrstuvwx", 
				"amount": 9900, 
				"description": "greg@interactiveties.com", 
				"source": { 
					"id": "card_123456789123456789123456", 
					"brand": "American Express", 
					"exp_month": 11, 
					"exp_year": 2021, 
					"last4": "2222", 
					"name": "greg@interactiveties.com"
				}, 
				"status": "succeeded", 
				"type": "charge.succeeded" 
			}
		- queries for the Contact with the email address provided in the description key/value pair from the JSON request
		- creates a Task
		- returns JSON similar to:
			{
				"status": "success", 
				"contactid": "0033000000YVLQWAA5", 
				"taskid": "00T3000000rEvJtEAK", 
				"message": ""
			}
*/
//@RestResource annotation exposes the class as a REST resource
@RestResource(urlMapping='/demo/createTask/*') //endpoint definition > {Salesforce Base URL}/services/apexrest/demo/createTask/
global class createTask {
	
	//primary logic for the class
	@HttpPost //HttpPost annotation exposes the method as a REST resource and called when an HTTP POST request is sent
	global static responseWrapper taskCreationLogic() {
		
		RestRequest req = RestContext.request; //the RestRequest for the Apex REST method
		responseWrapper responseJSON = new responseWrapper(); //responseWrapper object for API response
		
		String typeOfCard = ''; //placeholder for the type of card string
		String last4OfCard = ''; //placeholder for the last four digits of the card
		String emailAddress = ''; //placeholder for an email address
		Map<String, Object> body = new Map<String, Object>(); //placeholder for the JSON Body of the request
		Map<String, Object> src = new Map<String, Object>(); //placeholder for the source object from the JSON request
		
		String jsonBody = req.requestBody.toString(); //the body of the request
		
		if (!String.isBlank(jsonBody)) { //if the request body is NOT white space, empty ('') or null
			body = (Map<String, Object>)JSON.deserializeUntyped(jsonBody); //deserializes the JSON string into collections of primitive data types
			if (body.containsKey('description')) { //if there is a key of description in our body map
				emailAddress = (String)body.get('description'); //grab the value for the description key from the body map and cast it to a string
				List<Contact> queriedContacts = [SELECT Id FROM Contact WHERE Email = :emailAddress ORDER BY CreatedDate DESC LIMIT 1]; //query for a Contact that has the email address
				if (!queriedContacts.isEmpty()) { //if the list is not empty
					if (body.containsKey('source')) { //if there is a key of source in our body map
						src = (Map<String, Object>)body.get('source'); //grab the value for the source key from the body map and cast it to a new map (String > primative data types)
						if (src.containsKey('brand')) { //if there is a key of brand in our src map
							typeOfCard = (String)src.get('brand'); //grab the value for the brand key from the src map and cast it to a string
						}
						if (src.containsKey('last4')) { //if there is a key of last4 in our src map
							last4OfCard = (String)src.get('last4'); //grab the value for the last4 key from the src map and cast it to a string
						}
					}
					
					responseJSON.contactid = queriedContacts[0].Id; //populate the Id of the Contact record to our response object
					
					Task newTask = new Task(ActivityDate = Date.Today(), Description = 'The '+typeOfCard+' credit card ending in '+last4OfCard+' was charged.', Status = 'Complete', Subject = typeOfCard+' Card Charged', WhoId = queriedContacts[0].Id); //create a Task
					
					Database.SaveResult insertNewTask = Database.insert(newTask); //insert the new Task
					if (!insertNewTask.isSuccess()) { //if the insert DML was NOT successful
						List<Database.Error> errors = insertNewTask.getErrors(); //grab the error array from the SaveResult object
						//respond with failure
						responseJSON.status = 'failure';
						responseJSON.message = errors[0].getMessage(); //set the message to the first error in the array
					} else { //otherwise, the insert was successful
						responseJSON.taskid = insertNewTask.getId(); //populate the Id of the Task record to our response object
					}
				} else { //otherwise, no key of source in our map
					//respond with failure
					responseJSON.status = 'failure';
					responseJSON.message = 'There are no Contacts with the email address of '+emailAddress+'.';
				}
			} else { //otherwise, no key of description in our map
				//respond with failure
				responseJSON.status = 'failure';
				responseJSON.message = 'No description in the JSON request.';
			}
		} else { //otherwise, the JSON body was white space, empty ('') or null
			//respond with failure
			responseJSON.status = 'failure';
			responseJSON.message = 'Things basically broke...';
		}
		return responseJSON; //return the JSON response
	}
	
	//wrapper class for the response to an API request
	global class responseWrapper {
		
		global String status {get;set;} //status string
		global String contactid {get;set;} //18 character Contact record Id
		global String taskid {get;set;} //18 character Task record Id
		global String message {get;set;} //message string
		
		//constructor
		global responseWrapper() {
			//default all values
			this.status = 'success';
			this.contactid = '';
			this.taskid = '';
			this.message = '';
		}
	}

}

The aim of this post is to offer you an example that can be utilized alongside existing samples in the developer community. This is intended to enhance your understanding of the fundamental concepts and options available for developing REST API Web Services with Salesforce.