I am trying to set up a webhook from callrails platform. According to CallRail documentation it appears that my endpoint should response with a HTTP status code of 200? I've done webhooks before but never seen this one before. I am not getting anything after a call is made.
From call rails docs
"Your endpoint should respond with a HTTP status code of 2xx to indicate that the data was received properly. In general, a response status code other than 2xx indicates that the webhook was unable to complete the requested action."
This is the php code I am using. I just want to see the data being posted. Is there something I need to add to properly respond with 200 status code?
include('common.php');
if(!empty($_POST))
{
foreach($_POST as $key => $value)
{
$msg .= 'Key: ' . $key . ' => ' . $value . '<br>';
}
$ret = runner_mail(array('to' => 'xxxx', 'subject' => 'Callrail Call POST', 'htmlbody' => $msg));
$data = json_decode($_POST['body'], true);
if(!empty($data))
{
foreach($data as $key => $value)
{
$message .= 'Key: ' . $key . ' => ' . $value . '<br>';
}
$ret = runner_mail(array('to' => 'xxxxxx', 'subject' => 'Callrail Body Data', 'htmlbody' => $message));
}
}
Thanks for your feedback
for anyone that wants to know here is the answer.
$payload = #file_get_contents('php://input');
$call = json_decode($payload);
//process data
http_response_code(200);
Related
TLDR: Trying to post a tweet everytime someone posts on wordpress but can't authenticate correctly with oauth 1 to use the /2/tweets endpoint.
So I was annoyed when Jetpack social on my site decided to start charging for tweet scheduling. Usually I would be fine with this but it just seemed dumb that they were charging for something that I could build in a few days. So I'm planning on making an open source plugin that let's anyone use their wordpress as a tweet scheduler.
I'm currently struggling to properly authenticate with oauth to use the api v2. There's a lot of content out there for v1 but far less for v2 and particuarly v2 with wordpress.
Here's the resources I've used:
-https://www.youtube.com/watch?v=jOlJ5VJ_kSw for the oauth code
-https://developer.wordpress.org/reference/functions/wp_remote_post/ for the wp_remote_post() api call
-Numerous stack articles ie Wordpress twitter api 1.1 search class
which provides a good solution for 1.1 but I have made the possibly
false assumption based on what I've seen that 1.1 is now deprecated
-https://developer.twitter.com/en/docs/authentication/oauth-1-0a/creating-a-signature
shows you how to make a signature for a 1.1 endpoint to post a tweet.
I believe the problem is with the parameters I pass in for oauth, I used all that params from the above twitter docs article but that was 1.1 so possibly an issue.
Here's my code:
function focus_tweet_getSignature($endpoint, $method, $params, $focus_tweet_api_key_secret, $focus_tweet_access_token_secret)
{
uksort($params, 'strcmp');
foreach ($params as $key => $value) {
$params[$key] = rawurlencode($key) . '=' . rawurlencode($value);
}
$signatureBase = array(
rawurlencode($method),
rawurlencode($endpoint),
rawurlencode(implode('&', $params)),
);
$signatureBaseString = implode('&', $signatureBase);
$signatureKey = array(
rawurlencode($focus_tweet_api_key_secret),
rawurlencode($focus_tweet_access_token_secret),
);
$signatureKeyString = implode('&', $signatureKey);
return base64_encode(hash_hmac('sha1', $signatureBaseString, $signatureKeyString, true));
}
// 'oauth_consumer_key',
// 'oauth_nonce',
// 'oauth_signature',
// 'oauth_signature_method',
// 'oauth_timestamp',
// 'oauth_token',
// 'oauth_version',
// 'status'
function focus_tweet_getAuthorizationString($oauth)
{
$authorizationString = 'Authorization OAuth:';
$count = 0;
foreach ($oauth as $key => $value) {
$authorizationString .= !$count ? ' ' : ', ';
$authorizationString .= rawurlencode($key) . '="' . rawurlencode($value) . '"';
$count++;
}
return $authorizationString;
}
function focus_tweet_makeApiCall( $apiParams, $focus_tweet_status ){
$response = wp_remote_post($apiParams['endpoint'], array(
'method' => $apiParams['method'],
'headers' => array(
array(
'Accept: application/json',
$apiParams['authorization'],
'Expect:'
),
),
'body' => "{
'text': $focus_tweet_status
}",
)
);
//If response is not 200, trigger fatal error
if (is_wp_error($response) || wp_remote_retrieve_response_code($response) != 200) {
//Combine response details and build auth header function
$focus_tweet_res_body = wp_remote_retrieve_body($response);
wp_die($apiParams['authorization'] . '&' . $focus_tweet_res_body);
} else {
wp_die("Tweeted!");
}
}
function focus_tweet_tweet($focus_tweet_status)
{
$focus_tweet_api_key = '';
$focus_tweet_api_key_secret = '';
$focus_tweet_access_token = '';
$focus_tweet_access_token_secret = '';
$oauth = array(
'oauth_consumer_key' => $focus_tweet_api_key,
'oauth_nonce' => md5(microtime() . mt_rand()),
'oauth_signature_method' => 'HMAC-SHA1',
'oauth_token' => $focus_tweet_access_token,
'oauth_timestamp' => time(),
'oauth_version' => '1.0',
'status' => $focus_tweet_status,
);
$oauth['oauth_signature'] = focus_tweet_getSignature('https://api.twitter.com/2/tweets', 'POST', $oauth, $focus_tweet_api_key_secret, $focus_tweet_access_token_secret);
$apiParams = array(
'method' => 'POST',
'endpoint' => 'https://api.twitter.com/2/tweets',
'authorization' => focus_tweet_getAuthorizationString($oauth),
'url_params' => array(),
);
focus_tweet_makeApiCall($apiParams, $focus_tweet_status);
}
Triggered when I save a post for now while I'm testing.
Warning: Array to string conversion in C:\Users\Will\Local Sites\plugintestbench\app\public\wp-includes\class-requests.php on line 800
Authorization OAuth: oauth_consumer_key="", oauth_nonce="", oauth_signature_method="HMAC-SHA1", oauth_token="", oauth_timestamp="1672918493", oauth_version="1.0", status="Hello%20World%21", oauth_signature=""&{ "title": "Unauthorized", "type": "about:blank", "status": 401, "detail": "Unauthorized" }
The warning I believe is fine for now although will fix later.
I have used the credentials to post a tweet from postman so that's not the issue.
I initially believed that it was not possible to use curl in wordpress so may try switching to that although I really believe that the issue is with the oauth params.
(Including twitter/flickr api calls on every page load (WordPress))
Will keep this post updated while I work on this and will ofcourse post the finished github code once it's completed.
I'm trying to signin the user just with PHP and curl, without any external libraries for Twitter authentication. I follow these Twitter instructions. But I can't figure out how to complete step 3.
1. Obtaining a request token (done)
2. Redirecting the user (done)
3. Converting the request token to an access token. (done * problem)
Here is my code:
// API SETTINGS
$consumerSecret = 'xxx';
$consumerKey = 'xxx';
$oauth_signature_method = 'HMAC-SHA1';
if (!isset($_GET['oauth_verifier'])) {
// STEP 1 - TWITTER OBTAINING A REQUEST TOKEN
$callbackUrl = 'http://localhost/skeletons/webiik/example/login/';
$url = 'https://api.twitter.com/oauth/request_token';
// Data we will send
$data = [
'oauth_callback' => $callbackUrl,
'oauth_consumer_key' => $consumerKey,
'oauth_signature_method' => $oauth_signature_method,
'oauth_timestamp' => time(),
'oauth_nonce' => $token->generate(3),
'oauth_version' => '1.0',
];
// Sort data alphabetically, because Twitter requires that
ksort($data);
// Generate signature and add it to data array
$signData = 'POST&' . urlencode($url) . '&' . urlencode(http_build_query($data));
$secret = '';
$signKey = urlencode($consumerSecret) . '&' . urlencode($secret);
$data['oauth_signature'] = base64_encode(hash_hmac('sha1', $signData, $signKey, true));
// Prepare http headers from data
$httpHeaders = [];
foreach ($data as $key => $value) {
$httpHeaders[] = urlencode($key) . '="' . urlencode($value) . '"';
}
// Add OAuth header with all data
$httpHeaders = 'Authorization: OAuth ' . implode(', ', $httpHeaders);
// Send post request to Twitter API with http headers and data
$res = $http->post($url, ['httpHeaders' => [$httpHeaders]], []);
// If we got some error, show error message and stop
if (count($res['err']) > 0) {
echo $res['err'];
exit;
}
// Prepare data for step 2 and 3 from Twitter's response
parse_str($res['body'], $res);
$oauth_callback_confirmed = $res['oauth_callback_confirmed'];
$oauth_request_token = $res['oauth_token'];
// Store oauth_token_secret into session, we will need it in step 3
$this->sessions->setToSession('oauth_token_secret', $res['oauth_token_secret']);
$this->sessions->setToSession('oauth_token', $oauth_request_token);
// STEP 2 - REDIRECTING THE USER TO TWITTER LOGIN
header('HTTP/1.1 302 Found');
header('Location: https://api.twitter.com/oauth/authenticate?oauth_token=' . urlencode($oauth_request_token));
}
// STEP 3 - CONVERTING THE REQUEST TOKEN TO AN ACCESS TOKEN
$url = 'https://api.twitter.com/oauth/access_token';
$oauth_token = $_GET['oauth_token'];
$oauth_verifier = $_GET['oauth_verifier'];
// Data we will send
$data = [
'oauth_consumer_key' => $consumerKey,
'oauth_nonce' => $token->generate(3),
'oauth_signature_method' => $oauth_signature_method,
'oauth_timestamp' => time(),
'oauth_token' => $oauth_token,
'oauth_version' => '1.0',
];
// Sort data alphabetically, because Twitter requires that
ksort($data);
// Generate signature and add it to data array
$signData = 'POST&' . urlencode($url) . '&' . urlencode(http_build_query($data));
$secret = $this->sessions->getFromSession('oauth_token_secret');
$signKey = urlencode($consumerSecret) . '&' . urlencode($secret);
$data['oauth_signature'] = base64_encode(hash_hmac('sha1', $signData, $signKey, true));
// Sort data also with added oauth_signature, just for sure
ksort($data);
// Prepare http headers from data
$httpHeaders = [];
foreach ($data as $key => $value) {
$httpHeaders[] = urlencode($key) . '="' . urlencode($value) . '"';
}
// Add OAuth header with all data
$httpHeaders = ['Authorization: OAuth ' . implode(', ', $httpHeaders)];
$httpHeaders[] = 'Content-Length: ' . strlen('oauth_verifier=' . urlencode($oauth_verifier));
$httpHeaders[] = 'Content-Type: application/x-www-form-urlencoded';
// Add oauth_verifier to POST data
$postData = ['oauth_verifier' => $oauth_verifier];
// Send post request to Twitter API with http headers and data
$res = $http->post($url, ['httpHeaders' => $httpHeaders], $postData);
// If we got some error, show error message and stop
if (count($res['err']) > 0) {
echo $res['err'];
exit;
}
print_r($res);
Edit 1:
$http is just object that creates standard curl requests. In third step the curl request looks like:
$curl = curl_init($url);
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_FAILONERROR => 1,
CURLOPT_VERBOSE => 1,
CURLOPT_HEADER => 1,
CURLOPT_HTTPHEADER => $arrayOfHttpHeaders,
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => http_build_query($postData),
]);
$response = curl_exec($curl);
// Process response...
Edit 2:
I just updated the code to be more accurate to Twitter's example. Now I really don't know where can be the problem.
Edit 3:
Solution: The code is ok. I had problem with router, which added slash at the end of URL. And I grabbed verifier_token with that slash.
How can I get headers sent by GuzzleHttp\Client()->get('URL')?
I can only get response headers, but not request. Help! thanks.
You can call the getHeaders() method of Request to get all the headers in a request, if you want to specifically check for a header you can use hasHeader()
use GuzzleHttp\Psr7;
$request = new Psr7\Request('GET', 'URL', ['Foo' => 'Bar']);
//Check for a header
if ($request->hasHeader('Foo')) {
echo 'Yes.';
}
//Get all the headers
foreach ($request->getHeaders() as $name => $values) {
echo $name .': ' . implode(', ', $values) . "\r\n";
}
I am basically trying to use the PAY call of Adaptive Payments to programmatically and immediately send funds from my own paypal account to other accounts. According to the documentation, so long as I specify the senderEmail (my own paypal address, used to set up the Adaptive Payments), this should work verbatim.
However, when I make the call, I always get result "CREATED" instead of "COMPLETED". Created means the system still wants me to manually log into PayPal and approve the payments. I really need these payments to occur automatically on the spot. Any help would be appreciated.
Here is my request string:
currencyCode=USD&
returnUrl=http%3A%2F%2Fwww.website.com%2F&
actionType=PAY&
cancelUrl=http%3A%2F%2Fwww.website.com%2F&
receiverList.receiver%280%29.email=receiver%40gmail.com&
receiverList.receiver%280%29.amount=1.00&
requestEnvelope.senderEmail=me%40gmail.com&
clientDetails.deviceId=mydevice&
clientDetails.ipAddress=127.0.0.1&
clientDetails.applicationId=APP-ZZZZZZZZZZZZZ&
requestEnvelope.errorLanguage=en_US&
memo=memo&
feesPayer=EACHRECEIVER&
ipnNotificationUrl=http%3A%2F%2Fwww.website.com%2Fpay.php
And here is the response from PayPal:
[responseEnvelope.timestamp] => 2012-03-01T19:09:57.290-08:00
[responseEnvelope.ack] => Success
[responseEnvelope.correlationId] => 71efd416a2100
[responseEnvelope.build] => 2486531
[payKey] => AP-ZZZZZZZZZZZZZZZ
[paymentExecStatus] => CREATED
Forget everything I said earlier. The problem isn't an inconsistency between Sandbox and Live either, but rather a wrong parameter for 'senderEmail'.
Simply change:
requestEnvelope.senderEmail=me#gmail.com&
To:
senderEmail=me#gmail.com&
For example, the following returns a 'COMPLETED' implicit payment.
<?php
function AdaptiveCall($bodyparams, $method, $payKey) {
try
{
$body_data = http_build_query($bodyparams, "", chr(38));
$url = trim("https://svcs.sandbox.paypal.com/AdaptivePayments/".$method."");
$params = array("http" => array(
"method" => "POST",
"content" => $body_data,
"header" => "X-PAYPAL-SECURITY-USERID: xxxxxxxxx\r\n" .
"X-PAYPAL-SECURITY-SIGNATURE: xxxxxxxxxxx\r\n" .
"X-PAYPAL-SECURITY-PASSWORD: xxxxxxx\r\n" .
"X-PAYPAL-APPLICATION-ID: APP-80W284485P519543T\r\n" .
"X-PAYPAL-REQUEST-DATA-FORMAT: NV\r\n" .
"X-PAYPAL-RESPONSE-DATA-FORMAT: NV\r\n"
)
);
//create stream context
$ctx = stream_context_create($params);
//open the stream and send request
$fp = #fopen($url, "r", false, $ctx);
//get response
$response = stream_get_contents($fp);
//check to see if stream is open
if ($response === false) {
throw new Exception("php error message = " . "$php_errormsg");
}
//close the stream
fclose($fp);
//parse the ap key from the response
$keyArray = explode("&", $response);
foreach ($keyArray as $rVal){
list($qKey, $qVal) = explode ("=", $rVal);
$kArray[$qKey] = $qVal;
}
//print the response to screen for testing purposes
If ( $kArray["responseEnvelope.ack"] == "Success") {
echo "<strong>".$method ."</strong><br>";
foreach ($kArray as $key =>$value){
echo $key . ": " .$value . "<br/>";
}
// Return payKey
global $payKey;
if(!empty($kArray['payKey'])) { $payKey = $kArray['payKey']; return($payKey); }
}
else {
echo 'ERROR Code: ' . $kArray["error(0).errorId"] . " <br/>";
echo 'ERROR Message: ' . urldecode($kArray["error(0).message"]) . " <br/>";
}
}
catch(Exception $e) {
echo "Message: ||" .$e->getMessage()."||";
}
}
//Create Pay body
$bodyparams = array ( "requestEnvelope.errorLanguage" => "en_US",
'actionType' => 'PAY',
'currencyCode' => 'USD',
'receiverList.receiver(0).email' => 'another_account#domain.tld',
'receiverList.receiver(0).amount' => '1.00',
'senderEmail' => 'xxxxxxxxx',
'memo' => 'Test memo',
'ipnNotificationUrl' => 'http://xxxxxxxx',
'cancelUrl' => 'http://xxxxxxxxx',
'returnUrl' => 'http://xxxxxxxxxx'
);
// Call Pay API
AdaptiveCall($bodyparams, "Pay");
?>
Pay response:
responseEnvelope.timestamp: 2012-03-03T09%3A10%3A22.900-08%3A00
responseEnvelope.ack: Success
responseEnvelope.correlationId: 4bc5cfc4a7514
responseEnvelope.build: 2486531
payKey: AP-1XJ7636763429720C
paymentExecStatus: COMPLETED
I am using the PayPal Pay API, with Adaptive (Chained) Payments. I am trying to forward a user to paypal and afterwards back to my predefined return_url.
The problem is: I need to have a PayKey within my return-url. Reason for that: I need to call a PaymentDetail API to review the payment within the return_url. And, I don't want to use IPN since I need the validation with some token right on my return Url.
The problem I have is: The PayKey is beeing generated with all the parameters, including the return-url (hence after I build the actual array from which I get my $response from. I can't put the PayKey in the return-Url since it's not generated at this point yet.
//Create request payload with minimum required parameters
$bodyparams = array ("requestEnvelope.errorLanguage" => "en_US",
"actionType" => "PAY",
"currencyCode" => "USD",
"cancelUrl" => "http://www.paypal.com",
"returnUrl" => $return_url . "&payKey=${payKey}", **// Does not work - PAYKEY NEEDED TO ADD???**
"receiverList.receiver(0).email" => "account1#hotmail.com", //TODO
"receiverList.receiver(0).amount" => $price, //TODO
"receiverList.receiver(0).primary" => "true", //TODO
"receiverList.receiver(1).email" => "account2#hotmail.com", //TODO
"receiverList.receiver(1).amount" => $receiver_gets, //TODO
"receiverList.receiver(1).primary" => "false" //TODO
);
// convert payload array into url encoded query string
$body_data = http_build_query($bodyparams, "", chr(38)); // Generates body data
try
{
//create request and add headers
$params = array("http" => array(
"method" => "POST",
"content" => $body_data,
"header" => "X-PAYPAL-SECURITY-USERID: " . $API_UserName . "\r\n" .
"X-PAYPAL-SECURITY-SIGNATURE: " . $API_Signature . "\r\n" .
"X-PAYPAL-SECURITY-PASSWORD: " . $API_Password . "\r\n" .
"X-PAYPAL-APPLICATION-ID: " . $API_AppID . "\r\n" .
"X-PAYPAL-REQUEST-DATA-FORMAT: " . $API_RequestFormat . "\r\n" .
"X-PAYPAL-RESPONSE-DATA-FORMAT: " . $API_ResponseFormat . "\r\n"
));
//create stream context
$ctx = stream_context_create($params);
//open the stream and send request
$fp = #fopen($url, "r", false, $ctx);
//get response
$response = stream_get_contents($fp);
//check to see if stream is open
if ($response === false) {
throw new Exception("php error message = " . "$php_errormsg");
}
fclose($fp);
//parse the ap key from the response
$keyArray = explode("&", $response);
foreach ($keyArray as $rVal){
list($qKey, $qVal) = explode ("=", $rVal);
$kArray[$qKey] = $qVal;
}
//set url to approve the transaction
$payPalURL = "https://www.sandbox.paypal.com/webscr?cmd=_ap-payment&paykey=" . $kArray["payKey"]; **// Here it works fine, since the PayKey is generated at this point ...**
//print the url to screen for testing purposes
If ( $kArray["responseEnvelope.ack"] == "Success") {
echo '<p>' . $payPalURL . '</p>';
}
else {
echo 'ERROR Code: ' . $kArray["error(0).errorId"] . " <br/>";
echo 'ERROR Message: ' . urldecode($kArray["error(0).message"]) . " <br/>";
}
Can somebody help?
I was at this for ages too myself. I finally figured it out. Paypal docs are hard to follow. I found the answer in the paypal adaptive pdf guide which I downloaded. It specifies to add payKey=${payKey} to the end of your return_url. I just tried it there and the paypal get request to my return url now contains the paykey.
So in rails which I'm using the return_url looks like this. Writing a php variable (I think) into the url as instructed by the guide
:return_url => "http://***********.com/paypal-return?payKey=${payKey}"
It seems that you're missing a step in the sequence.
The first step is to send your transaction parameters to
https://svcs.paypal.com/AdaptivePayments/Pay&yourtransactionparameters=blah
[sandbox]https://svcs.sandbox.paypal.com/AdaptivePayments/Pay&yourtransactionparameters=blah
You'll get the paykey in this response.
Once you've retrieved the paykey successfully, you'll call:
https://www.paypal.com/webscr&cmd=_ap-payment&paykey=xxxx
[sandbox]https://www.sandbox.paypal.com/webscr&cmd=_ap-payment&paykey=xxxx
In the second call, the payKey represents the rest of your transaction so you don't have to build another giant query string.
Change your code for:
//Create request payload with minimum required parameters
$bodyparams = array ("requestEnvelope.errorLanguage" => "en_US",
"actionType" => "PAY",
"currencyCode" => "USD",
"cancelUrl" => "http://www.paypal.com",
"returnUrl" => $return_url . '&payKey=${payKey}', **// That's right**
"receiverList.receiver(0).email" => "account1#hotmail.com", //TODO
"receiverList.receiver(0).amount" => $price, //TODO
"receiverList.receiver(0).primary" => "true", //TODO
"receiverList.receiver(1).email" => "account2#hotmail.com", //TODO
"receiverList.receiver(1).amount" => $receiver_gets, //TODO
"receiverList.receiver(1).primary" => "false" //TODO
);
PHP interprets ${payKey} as variable between the double quotes.
Change double quotes (") for simple quotes (')
Apparently you can embed dynamic variables in the return URL: https://www.x.com/thread/49785
Unfortunately, {$payKey} is invalid. so it must be $paykey or $pay_key. Good luck!