I hope someone on this forum has ever worked with credit card payments using Cybersource. I can't seem to get around Bad Request.
As always, I start testing REST services with Postman. Cybersource provides a Postman collection for testing purposes. The services have no Authorization.
This works as expected. This particular service returns a JWT:

Digest and Signature headers are dynamically calculated, but that works correctly in my PL/SQL code (verified by comparing the Postman values and the values in my PL/SQL code).
However, if I copy these Postman headers and body values to this PL/SQL block, I get a Bad Request response:
declare
l_http_request utl_http.req;
l_http_response utl_http.resp;
l_response_text varchar2(32767);
l_name varchar2(256);
l_value varchar2(1024);
begin
-- Open a HTTP request
utl_http.set_wallet ( path => epk_conf.get_value ('WALLET_PATH'));
l_http_request := utl_http.begin_request(url => 'https://apitest.cybersource.com/microform/v2/sessions', method => 'POST');
-- Set headers
utl_http.set_header(l_http_request, 'Content-Type', 'application/json');
utl_http.set_header(l_http_request, 'v-c-merchant-id', 'testrest');
utl_http.set_header(l_http_request, 'date', 'Thu, 15 Feb 2024 13:11:14 GMT');
utl_http.set_header(l_http_request, 'host', 'apitest.cybersource.com');
utl_http.set_header(l_http_request, 'digest', 'SHA-256=HsFck9S8d4KQ3gDgLrc0coCXN7DDfsRXT1I5PnLpb8U=');
utl_http.set_header(l_http_request, 'signature', 'keyid="08c94330-f618-42a3-b09d-e1e43be5efda", algorithm="HmacSHA256", headers="host date request-target digest v-c-merchant-id", signature="vk7RSJ4ydOxloE6yOnb/GL8DlEL3fhgp7ey3c4xx1AA="');
-- Set the JSON body
utl_http.write_text(l_http_request, '{"targetOrigins":["https://www.test.com"],"allowedCardNetworks":["VISA","MAESTRO","MASTERCARD"],"clientVersion":"v2.0"}' );
-- Get the HTTP response
l_http_response := utl_http.get_response(l_http_request);
dbms_output.put_line('HTTP response status code: ' || l_http_response.status_code);
-- Read the response
utl_http.read_text(l_http_response, l_response_text, 32766);
dbms_output.put_line('Response: '||l_response_text);
-- Close HTTP response
utl_http.end_response(l_http_response);
end;
This returns a status 400:
HTTP response status code: 400
Response: {"response":{"rmsg":"Bad Request"}}
I can't figure out why using utl_http doesn't work.