Why This Is Useful
Sooner or later Salesforce needs to send data somewhere rather than just receive it. Pushing an Account to an ERP, notifying a fulfilment system, firing something at a webhook.
That’s a POST request, and the mechanics in Apex are pleasantly simple once you’ve seen them once. Let’s go through it properly, including the bit most tutorials skip: how to do it without hardcoding credentials.
All the code is on GitHub.
The Code
public with sharing class POST_Example
{
public void sendPOSTReq()
{
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:SalesforcePOST');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
String acctToPass = JSON.serialize(wrapAccount(generateAccount()));
System.debug('This is the account JSON ::: ' + acctToPass);
req.setBody(acctToPass);
Http http = new Http();
HttpResponse res = http.send(req);
System.debug(res.getBody());
}
private Account generateAccount()
{
Account acct = new Account();
acct.Name = 'Hi';
acct.Phone = '8162221111';
return acct;
}
private AccountWrapper wrapAccount(Account acct)
{
AccountWrapper wrapper = new AccountWrapper();
wrapper.acct = acct;
return wrapper;
}
}And the wrapper, which is all of four lines:
public with sharing class AccountWrapper
{
public Account acct;
}Walking Through It
new HttpRequest() – the request you’re about to send.setEndpoint('callout:SalesforcePOST') – where it goes. That callout: prefix is the important bit and we’ll come back to it.setMethod('POST') – GET, POST, PUT, PATCH, DELETE all work here.setHeader('Content-Type', 'application/json') – tells the receiving system what you’re sending. Skip this and a lot of APIs will reject you with a confusing error rather than a helpful one.JSON.serialize(...) – turns your Apex object into a JSON string. Note we serialize a wrapper class rather than building JSON by hand, because building JSON with string concatenation is how you end up debugging a missing comma at midnight.new Http().send(req) – actually sends it, and hands you back an HttpResponse.
That callout: Prefix Is The Whole Point
You could write a full URL there. Please don’t.callout:SalesforcePOST refers to a Named Credential, and it buys you three things:
1. No credentials in your code. Salesforce handles authentication and injects the header for you. Nothing sensitive lives in Apex, and nothing sensitive ends up in your Git history.
2. No Remote Site Setting to remember. Named Credentials handle that too.
3. Different endpoints per environment. Your sandbox Named Credential points at the test API, production points at the real one. Same code, no conditionals.
That third one is worth the setup on its own. I’ve written up how to use Named Credentials properly, and if you’re currently storing API keys in a Custom Setting, go read that next.
If you do hardcode a URL, you’ll need to add it under Setup → Remote Site Settings or Salesforce will refuse the callout entirely.
Actually Handle The Response
The example above debugs the body and moves on, which is fine for a demo and not fine in production. External systems fail. Here’s what you actually want:
HttpResponse res = new Http().send(req);
if (res.getStatusCode() >= 200 && res.getStatusCode() < 300)
{
System.debug('Success ::: ' + res.getBody());
}
else
{
System.debug('Failed with ' + res.getStatusCode() + ' ::: ' + res.getStatus());
//log it somewhere you'll actually see it
}Wrap the send in a try/catch too, because System.CalloutException is thrown when the endpoint is unreachable or times out, which is not the same as getting a 500 back.
The Rules That Will Trip You Up
You cannot make a callout after DML in the same transaction. This is the big one. Insert a record then try to call out and you’ll get “You have uncommitted work pending.” The fix is to go async, usually a Queueable with Database.AllowsCallouts, which I covered in the asynchronous Apex guide.
100 callouts per transaction, 120 seconds total. Never put a callout in a loop, for the same reasons you never put SOQL in a loop.
Callouts don’t work in triggers unless they’re async. A trigger is part of a transaction that hasn’t committed yet.
Tests can’t make real callouts. You must mock them with HttpCalloutMock:
@IsTest
private class POST_ExampleTest
{
private class MockResponse implements HttpCalloutMock
{
public HttpResponse respond(HttpRequest req)
{
HttpResponse res = new HttpResponse();
res.setStatusCode(200);
res.setBody('{"success":true}');
return res;
}
}
@IsTest
static void itSendsThePost()
{
Test.setMock(HttpCalloutMock.class, new MockResponse());
Test.startTest();
new POST_Example().sendPOSTReq();
Test.stopTest();
}
}Which is a nice practical example of dependency inversion, incidentally. Salesforce built a seam so you can swap the real HTTP layer for a fake one in tests.
Full details in the Apex callouts documentation. Now go send some data somewhere.
Get Coding With The Force Merch!!
We now have a redbubble store setup so you can buy cool Coding With The Force merchandise! Please check it out! Every purchase goes to supporting the blog and YouTube channel.
Get Shirts Here!
Get Cups, Artwork, Coffee Cups, Bags, Masks and more here!
Check Out More Coding With The Force Stuff!
If you liked this post make sure to follow us on all our social media outlets to stay as up to date as possible with everything!
Youtube
Patreon
Github
Facebook
Twitter
Instagram
Salesforce Development Books I Recommend
Advanced Apex Programming
Salesforce Lightning Platform Enterprise Architecture
Mastering Salesforce DevOps
Good Non-SF Specific Development Books:
Clean Code
Clean Architecture