Hi everyone! In this post I’d like to walk you through the configurations needed to integrate a third party service with Salesforce by using webhooks. There are plenty similar articles out there. However, when I tried to do it myself I had to collect information from different sites in order to get it to work.
Hopefully in this guide I’ll be able to provide everything you need to process inbound webhook in Salesforce.
So webhooks (also called a web to callback or HTTP push API) are typically used to provide real-time information to other applications such as Salesforce. A webhook delivers data as it happens so you get immediate access to it.
Please note that webhook integration is a fire & forget type of integration. It does not require any kind of authentication. Make sure it fits your security standards & consider potential risks. You definitely don’t want to have a public URL that returns a list of contacts upon request. But a function that makes updates internally may be just fine. I understand that not every service offers more secure integration type such as oAuth 2.0, but please take your time to think through data security.
For example:
- You use marketing tools outside of CRM and someone clicked the link from your email campaign. You’d like to record this event as activity on lead/contact in Salesforce.
- You have a web-form on your website pages. When someone submits the form you’d like to create a lead in Salesforce.
- You get the point…
Essentially it is a JSON that contains event information that is sent to another application. In our case it’s Salesforce.
There are two main components to this process:
- Configure Salesforce to process third party notifications.
- Configure third-party service to send event notifications.
Alright, I think we are ready to get started. You’ll need ‘System Administrator’ access in Salesforce as well as admin account in third-party service you’re integrating.
Step 1: Configure Public Site URL
In order to get this to work, we need to do register public URL. This URL will be used to push updates to Salesforce.
Go to Setup -> Sites
If it’s your first time configuring sites then it is likely that you’ll see the following notice:

Go ahead and accept Salesforce terms of use and click ‘Register My Salesforce Site Domain‘.
Press ‘New’ to add new site:

Set site configurations as follows:
| Site Label | Webhooks |
| Site Contact | doesn’t matter |
| Default Record Owner | doesn’t matter |
| Default Web Address | webhooks |
| Active | True |
| Active Site Home Page | SiteLogin (No one is actually will be able to login) |
| Clickjack protection level | pick most secure |
Overall, take a look at the screenshot below for site settings.

I’m not sure if you can tell from the screenshot. But my SF developer org base site URL is:
https://dev-ed.my.salesforce-sites.com.
We just created site page with /webhooks add-on.
This means our full site URL for webhooks is
https://dev-ed.my.salesforce-sites.com/webhooks.
Please note, that this ‘/webhooks’ site can be used for many webhook integrations from different services. It is not limited to just one integration. Therefore you don’t need to create a separate site for each webhook. Later in this guide I’ll walk you through on how integrations are separated by having different URLs.
So for now, we are good with site configurations. We’ll come back to it once we configure other things.
Step 2: Configure REST Resource In Apex
Let’s create an apex class to process incoming callouts. This is will be our main place to process JSON data that is sent from external service.
Go ahead and create first apex class that is: WebhookListner
@RestResource(urlMapping ='/application-name')
global without sharing class WebhookListner{
/**
* @description this is POST method.
*/
@HttpPost
global static void doGet(){
// Your awesome code goes here
}
}This is all you need to start processing requests. Just simply add your logic in doGet() method. You can read more about @RestResource annotation in SF documentation.
Notice how I have urlMapping attribute set to
'/application-name'. This is how you can separate different webhook integrations. You can have a different apex class with different urlMapping that still uses ‘webhooks’ site URL.Earlier in this guide we’ve set our webhooks site to have the following URL:
https://dev-ed.my.salesforce-sites.com/webhooks
So the final URL that points to our apex class with URL mapping will look like this:
https://dev-ed.my.salesforce-sites.com/webhooks/services/apexrest/application-name
Now we just need to uncover how you’d actually process callouts as they come in from external service.
Step 3: Configure Webhook Callout Within Third-Party App
To make it more relatable, let’s pretend that I’m integrating with marketing service to track email clicks. My marketing platform allows me to send webhook notifications when someone clicks any link in my email.
Obviously, I don’t know what your service user interface looks like. But typically in order to setup webhook integration all you need to do is to determine triggering event and supply the URL which will be used for callouts.
Here is a visual example for your reference:

As you can tell the key parameters are:
- Name: call it as you like
- Triggering Event: in my example it’s when someone clicks the link within the email
- URL: this will be our REST URL on Salesforce webhooks site:
https://dev-ed.my.salesforce-sites.com/webhooks/services/apexrest/application-name
Step 4: Determine What’s In Your JSON Payload
Let’s say the webhook event has been triggered at your third-party service. In our case someone clicked the link within the email. At that time webhook automation will send a JSON that contains all relevant information about the event to your exposed REST URL in Salesforce.
There are two main methods that I can suggest for you to determine incoming JSON structure.
Research API documentation
Most services provide API documentation with sample JSONs. You just need to find a section about webhooks and then find sample JSON for your use case.
Use Salesforce Debug Logs (Preferred Method)
So lets say you’ve been looking for documentation online but can’t seem to find where they talk about your specific use case. In this scenario, we can just setup debug logs in Salesforce and see what comes in.
Important! In order to see debug logs you must trace them under ‘Webhooks Site Guest User’.
This is because every time you create a site, Salesforce will create a guest user for it with it’s own guest profile. All updates in the system to happen as this guest user. The reason user is called ‘Webhooks Site Guest User’ is because our site name is ‘Webhooks’
Go to Setup -> Debug Logs -> New
- Traced Entity Type: User
- Traced Entity Name: Webhooks Site Guest User
- Start Date/Expiration Date
- Debug Level: Pick default (SFDC_DevConsole)

Make sure that the start/expiration times are set to when you actually going to run debug logs.
Now, let’s add debug logs to our apex class:
@RestResource(urlMapping ='/application-name')
global without sharing class WebhookListner{
/**
* @description this is POST method.
*/
@HttpPost
global static void doGet(){
// Debug log:
RestRequest request = RestContext.request;
System.debug('MyService.newMessage body:\n ' + request.requestBody.toString());
}
}Next step is to trigger event in your external application so that we can inspect debug logs. In my imaginary use case scenario I have to send myself an email and click the link within the email.
Here is what I can see in debug logs as I trigger the event in my external application:

So the JSON I’m getting in return is the following:
{
"event": "email.link.click",
"entity": {
"campaignName": "Email Newsletter",
"contact": {
"firstname": "John",
"lastname": "Doe",
"email": "test@test.com"
}
}
}Alright, let’s move on to a next step where we have to implement JSON processing.
Step 5: Process JSON
Since now we know what we are getting from external service, we have to turn incoming JSON into sObjects for further processing. To do that, let’s use json2Apex service to come up with a class structure. All you need to do is copy/paste your JSON and click ‘Generate Apex Code‘. Very easy!
Here is an example output:

I will make small alterations to object names in apex output to align with standard name conventions for apex classes.
Here is what I have for a class that is supposed to handle webhook responses:
public with sharing class WebhookResponse {
public String event; //email.link.click
public entityObj entity;
public class entityObj {
public String campaignName; //Email Newsletter
public contactObj contact;
}
public class contactObj {
public String firstname; // John
public String lastname; // Doe
public String email; // john.doe@email.com
}
public static WebhookResponse parse(String json){
return (WebhookResponse) System.JSON.deserialize(json, WebhookResponse.class);
}
}Step 6: Give Access to Apex Classes
So we have two classes at this time. One is set to intake incoming POST calls. Another one is to process JSON payloads. Both of those classes should be accessible by ‘Webhooks Profile’. You will not be able to find this profile within the list of other profiles.
You have to go to Setup -> Sites -> Webhooks
From Site details page you have to click ‘Public Access Settings‘ button. This will get you to a site profile page.

From there click ‘Apex Class Access‘. Select both: WebhookListner, WebhookResponse classes. Save.
Step 7: Process Webhook Callout
Let’s recap what we did so far.
- We’ve configured a webhook in external application
- Exposed REST resource link in Salesforce
- Figured out JSON payload
- Configured APEX to process callouts
Now we just need Salesforce to process webhooks & perform record updates. Let’s pretend that when someone clicks the email link I’d like to create an activity record under lead/contact in Salesforce. If there are no leads/contacts in the system then I should create a new lead and add activity record.
Depending on your use case you may need give appropriate access rights to objects and fields to your Webhooks Profile.
Here is a full code to process webhook callout:
@RestResource(urlMapping ='/application-name')
global without sharing class WebhookListner {
/**
* @description Application has a webhook that calls Salesforce whenever someone clicks a link from the email.
*/
@HttpPost
global static void doGet(){
RestRequest request = RestContext.request;
WebhookResponse responseWrapper = WebhookResponse.parse(request.requestBody.toString());
String contactEmail = responseWrapper.entity.contact.email;
String campaignName = responseWrapper.entity.campaignName;
// From the list of objects we need to extract email address
if(contactEmail != null){
List <Lead> leadsToInsert = new List <Lead>();
List <Event> eventsToInsert = new List <Event>();
// Query Leads and Contacts that match email address
List <Lead> leadList = [SELECT Id, OwnerId FROM Lead WHERE Email =: contactEmail AND isConverted = FALSE];
List <Contact> contactList = [SELECT Id, Account.OwnerId FROM Contact WHERE Email =: contactEmail];
for(Lead l : leadList){
eventsToInsert.add(getEventRecord(l.id, l.OwnerId, campaignName));
}
for(Contact c : contactList){
eventsToInsert.add(getEventRecord(c.id, c.Account.OwnerId, campaignName));
}
// When no leads/contacts found - create new lead record.
if(leadList.isEmpty() && contactList.isEmpty()){
Lead newLeadRecord = getLeadRecord(responseWrapper);
insert newLeadRecord;
eventsToInsert.add(getEventRecord(newLeadRecord.id, null, campaignName));
}
if(!eventsToInsert.isEmpty()){
insert eventsToInsert;
}
}
}
/**
* @description generates Event record
* @param parentRecordId Id of the lead/contact
* @param recordOwnerId Id of the event owner
* @param campaignName Campaign Name
* @return return Event record
*/
private static Event getEventRecord(Id parentRecordId, Id recordOwnerId, String campaignName){
// sObjName will always be Lead or Contact
String sObjName = parentRecordId.getSObjectType().getDescribe().getName();
Event eventRecord = new Event();
eventRecord.Subject = 'Email Click - Campaign: '+ campaignName;
eventRecord.Type = 'Other';
eventRecord.WhoId = parentRecordId;
eventRecord.Description = 'This ' + sObjName + ' clicked the link from Email Campaign: '+ campaignName;
eventRecord.DurationInMinutes = 1;
eventRecord.ActivityDateTime = System.now();
if(recordOwnerId != null){
eventRecord.OwnerId = recordOwnerId;
}
return eventRecord;
}
/**
* @description creates new Lead record. Company field value is set to email domain name
* since it's not included in payload.
* @param response webhook response
* @return return lead record
*/
private static Lead getLeadRecord(WebhookResponse response){
Lead leadRecord = new Lead();
leadRecord.FirstName = response.entity.contact.firstname;
leadRecord.LastName = response.entity.contact.lastname;
leadRecord.Email = response.entity.contact.email;
leadRecord.Company = leadRecord.Email.split('@').get(1);
leadRecord.LeadSource = 'Marketing';
return leadRecord;
}
}Basically it looks for leads/contacts with exact same email addresses as in JSON payload. If records are found, it will generate an activity record which has details about marketing campaign. If there are no leads and contacts found then it will generate new lead record. It will extract company name from email address and pre-set a few other fields. It will then insert an activity for it as well.
Later on Salesforce administrator can set a report to track campaign efficiency and use generated leads in other sales processes.
Webhook Security Considerations
As mentioned in the beginning of the article, there is no authentication needed for webhooks. Therefore you may need to consider risks associated with having public REST resource.
One viable option is to validate callouts by incoming IP address. Some services are set with static IPs that rarely change.
@RestResource(urlMapping ='/application-name')
global without sharing class WebhookListner{
/**
* @description this is POST method.
*/
@HttpPost
global static void doGet(){
RestRequest request = RestContext.request;
String acceptedIpAddress = label.Application_IP_Address; // Example: 192.168.1.1 (Coming from Custom Label)
String requestIpAddress = request.remoteAddress; // Example: 192.168.1.1 (Coming from RestRequest)
if(requestIpAddress == acceptedIpAddress ){
// do something here
} else {
// log unauthorized IP address
}
}
}If your application uses multiple random IPs then this method won’t work. It only works if you know exactly which IP(s) are being used to make callouts.
Alright, that’s it for today. You’ve learned how to integrate inbound webhooks with Salesforce. Now you just have to put this knowledge to the test. Please let me know if I missed something & happy coding!
Good afternoon Oleksandr Makarenko! Thank you for sharing your knowledge. I would be grateful to know where you programmed these code snippets and in which environment you ran them and where I should run them when I finish development.
Hi Hugo! This is just a sample code that I configured in trailhead environment. You can copy paste the code ‘as is’ to your sandbox environment.