How to Trigger a Marqet Workflow from Salesforce
Use this guide to configure Salesforce to automatically send a webhook to Marqet when a record is updated. No third-party tools required. Everything is built using native Salesforce Apex and Named Credentials.
Prerequisites
- System Administrator access in Salesforce
- Your Marqet webhook URL (found in your Marqet workflow settings)
- Access to the Salesforce Developer Console
Step 1: Get your Marqet webhook URL
In Marqet, navigate to your workflow settings and copy the webhook receiver URL. It will look like this:
https://marqet.marq.com/api/webhooks/catch/[your-unique-id]
Keep this handy — you'll need it in step 5.
Step 2: Create an External Credential
In Salesforce, go to Setup → Named Credentials → External Credentials tab → New
| Field | Value |
|---|---|
| Label | Marq_Webhook_Cred |
| Name | Marq_Webhook_Cred |
| Authentication Protocol | No Authentication |
Save, then scroll down to Principals → New:
| Field | Value |
|---|---|
| Parameter Name | anonymous |
| Sequence Number | 1 |
| Identity Type | Anonymous |
Save.
Step 3: Create a Named Credential
In Salesforce, go to Setup → Named Credentials → Named Credentials tab → New
| Field | Value |
|---|---|
| Label | Marq_Webhook |
| Name | Marq_Webhook |
| URL | https://marqet.marq.com |
| External Credential | Marq_Webhook_Cred |
Save.
Step 4: Grant permission set access in Salesforce
Users who trigger the webhook need access to the External Credential.
- Go to Setup → Permission Sets → New
- Name it
Webhook_Access, leave License as--None--, and save - Inside the permission set, click External Credential Principal Access → Edit
- Move
Marq_Webhook_Cred - anonymousto the Enabled column and save - Click Manage Assignments → Add Assignment, find your user, and assign
Note: Any user or automation profile that updates records which trigger the webhook must be assigned this permission set.
Step 5: Deploy the Queueable Apex class
This class handles building the payload and sending the HTTP POST to Marqet. It runs asynchronously so it doesn't block the Salesforce record transaction.
In Salesforce, go to Developer Console → File → New → Apex Class, name it WebhookQueueable, and paste the code for your trigger type below.
Option A: Opportunity stage change (e.g. Closed Won)
WebhookQueueable.cls
public class WebhookQueueable implements Queueable, Database.AllowsCallouts { private List<Id> recordIds; public WebhookQueueable(List<Id> ids) { this.recordIds = ids; } public void execute(QueueableContext ctx) { for (Id rId : recordIds) { Opportunity opp = [SELECT Id, Name, StageName, Amount, AccountId, OwnerId, CloseDate FROM Opportunity WHERE Id = :rId LIMIT 1]; Map<String, Object> payload = new Map<String, Object>{ 'event' => 'opportunity.closed_won', 'opportunityId' => opp.Id, 'name' => opp.Name, 'stage' => opp.StageName, 'amount' => opp.Amount, 'accountId' => opp.AccountId, 'ownerId' => opp.OwnerId, 'closeDate' => String.valueOf(opp.CloseDate), 'changedAt' => System.now().format() }; HttpRequest req = new HttpRequest(); req.setEndpoint('callout:Marq_Webhook/api/webhooks/catch/[your-unique-id]'); req.setMethod('POST'); req.setHeader('Content-Type', 'application/json'); req.setBody(JSON.serialize(payload)); Http http = new Http(); HttpResponse res = http.send(req); System.debug('Webhook response: ' + res.getStatusCode()); } } }
OpportunityWebhookTrigger.apxt (File → New → Apex Trigger, sObject: Opportunity)
trigger OpportunityWebhookTrigger on Opportunity (after update) { List<Id> changedIds = new List<Id>(); for (Opportunity newRec : Trigger.new) { Opportunity oldRec = Trigger.oldMap.get(newRec.Id); // Only fires when stage CHANGES to Closed Won if (newRec.StageName != oldRec.StageName && newRec.StageName == 'Closed Won') { changedIds.add(newRec.Id); } } if (!changedIds.isEmpty()) { System.enqueueJob(new WebhookQueueable(changedIds)); } }
Example payload received by Marqet:
json{ "event": "opportunity.closed_won", "opportunityId": "006...", "name": "Acme Corp Deal", "stage": "Closed Won", "amount": 15000, "accountId": "001...", "ownerId": "005...", "closeDate": "2026-05-27", "changedAt": "2026-05-27 17:59:44" }
Option B: Contact field change (e.g. status becomes Business Partner)
Update WebhookQueueable.cls with the following execute method, or create a second class (e.g. ContactWebhookQueueable) if you need both running simultaneously:
WebhookQueueable.cls (Contact version)
public class WebhookQueueable implements Queueable, Database.AllowsCallouts { private List<Id> recordIds; public WebhookQueueable(List<Id> ids) { this.recordIds = ids; } public void execute(QueueableContext ctx) { for (Id rId : recordIds) { Contact c = [SELECT Id, FirstName, LastName, Email, AccountId, Title, Phone FROM Contact WHERE Id = :rId LIMIT 1]; Map<String, Object> payload = new Map<String, Object>{ 'event' => 'contact.status_changed', 'contactId' => c.Id, 'firstName' => c.FirstName, 'lastName' => c.LastName, 'email' => c.Email, 'title' => c.Title, 'phone' => c.Phone, 'accountId' => c.AccountId, 'changedAt' => System.now().format() }; HttpRequest req = new HttpRequest(); req.setEndpoint('callout:Marq_Webhook/api/webhooks/catch/[your-unique-id]'); req.setMethod('POST'); req.setHeader('Content-Type', 'application/json'); req.setBody(JSON.serialize(payload)); Http http = new Http(); HttpResponse res = http.send(req); System.debug('Webhook response: ' + res.getStatusCode()); } } }
ContactWebhookTrigger.apxt (File → New → Apex Trigger, sObject: Contact)
trigger ContactWebhookTrigger on Contact (after update) { List<Id> changedIds = new List<Id>(); for (Contact newRec : Trigger.new) { Contact oldRec = Trigger.oldMap.get(newRec.Id); // Fires when Partner_Status__c changes to "Business Partner" // Replace Partner_Status__c with your actual field API name if (newRec.Partner_Status__c != oldRec.Partner_Status__c && newRec.Partner_Status__c == 'Business Partner') { changedIds.add(newRec.Id); } } if (!changedIds.isEmpty()) { System.enqueueJob(new WebhookQueueable(changedIds)); } }
Example payload received by Marqet:
json{ "event": "contact.status_changed", "contactId": "003...", "firstName": "Jane", "lastName": "Smith", "email": "jane.smith@example.com", "title": "VP of Partnerships", "phone": "+1 (415) 555-0100", "accountId": "001...", "changedAt": "2026-05-27 17:59:44" }
Tip: The field
Partner_Status__cis an example custom field name. Replace it with the actual API name of the field in your Salesforce org. You can find this under Setup → Object Manager → Contact → Fields & Relationships.
Option C: Account field change (e.g. Type changes to Partner)
AccountWebhookTrigger.apxt (File → New → Apex Trigger, sObject: Account)
trigger AccountWebhookTrigger on Account (after update) { List<Id> changedIds = new List<Id>(); for (Account newRec : Trigger.new) { Account oldRec = Trigger.oldMap.get(newRec.Id); // Fires when Type changes to "Partner" if (newRec.Type != oldRec.Type && newRec.Type == 'Partner') { changedIds.add(newRec.Id); } } if (!changedIds.isEmpty()) { System.enqueueJob(new WebhookQueueable(changedIds)); } }
Update WebhookQueueable.cls to query Account fields instead:
Account acc = [SELECT Id, Name, Type, Industry, OwnerId, BillingCity, BillingCountry FROM Account WHERE Id = :rId LIMIT 1]; Map<String, Object> payload = new Map<String, Object>{ 'event' => 'account.type_changed', 'accountId' => acc.Id, 'name' => acc.Name, 'type' => acc.Type, 'industry' => acc.Industry, 'ownerId' => acc.OwnerId, 'city' => acc.BillingCity, 'country' => acc.BillingCountry, 'changedAt' => System.now().format() };
Step 6: Test the integration
To verify the connection before testing a live record, run this in Developer Console → Debug → Open Execute Anonymous Window:
apexHttpRequest req = new HttpRequest(); req.setEndpoint('callout:Marq_Webhook/api/webhooks/catch/[your-unique-id]'); req.setMethod('POST'); req.setHeader('Content-Type', 'application/json'); req.setBody('{"event": "test", "message": "Salesforce connection test"}'); Http http = new Http(); HttpResponse res = http.send(req); System.debug('Status: ' + res.getStatusCode()); System.debug('Body: ' + res.getBody());
Check the Logs tab — you want to see Status: 200. If Marqet receives the payload, the Named Credential is configured correctly.
Then do a live record test:
- Open an Opportunity (or Contact/Account depending on your trigger)
- Update the relevant field and save
- Check your Marqet workflow — the trigger should fire within a few seconds
Troubleshooting
| Error | Likely cause | Fix |
|---|---|---|
We couldn't access the credential |
Permission set not assigned | Assign Webhook_Access permission set to the user (step 4) |
Duplicate value found on deploy |
Class or trigger already exists | Open the existing file via File → Open and overwrite the code |
| No payload in Marqet after record save | Trigger condition not met, or wrong field name | Check the Developer Console log for a USER_DEBUG line with the HTTP status |
Status: 404 |
Webhook path mismatch | Verify the path in your Apex class matches your Marqet URL exactly |
Status: 401 or 403 |
Auth issue on Marqet's side | Confirm the webhook URL is active in your Marqet workflow settings |
Customizing the trigger condition
The trigger fires based on a field value change. The general pattern is:
apexif (newRec.YOUR_FIELD__c != oldRec.YOUR_FIELD__c && newRec.YOUR_FIELD__c == 'YOUR_TARGET_VALUE') { changedIds.add(newRec.Id); }
To find the correct API field name, go to Setup → Object Manager → [Object] → Fields & Relationships and look in the Field Name column.
You can also trigger on any field change without requiring a specific value:
apex// Fires whenever the field changes to anything if (newRec.YOUR_FIELD__c != oldRec.YOUR_FIELD__c) { changedIds.add(newRec.Id); }
Adding payload fields
To include additional Salesforce fields in the payload sent to Marqet, add them to the SELECT query in WebhookQueueable and the payload map:
// In the SELECT: Contact c = [SELECT Id, Email, Custom_Field__c FROM Contact WHERE Id = :rId LIMIT 1]; // In the payload map: 'customField' => c.Custom_Field__c,
For questions or setup support, contact the Support team at support@marq.com