I have a Webhook on my Xero Account for Invoices. When a new invoice is created I want to access the Xero Api using the data from the Webhook to Send the user the invoice email. I have all the code for this but my issue is that the Webhook expects a response of 200 and when I call the Xero Api code from within the webhook I get an error on Xero saying the response contained a body. This makes sense as I'm submitting and getting data from the Api.
So how can I make my requests to the Xero Api without interfering with the Xero Webhook response?
Webhook Code:
<?php
///////////////////////////////////////////////////////////////////////////
// WEBHOOK AUTHENTICATION - START
///////////////////////////////////////////////////////////////////////////
//hook section
$rawPayload = file_get_contents("php://input");
// Update your webhooks key here
$webhookKey = 'myWebhookKey';
// Compute the payload with HMACSHA256 with base64 encoding
$computedSignatureKey = base64_encode(
hash_hmac('sha256', $rawPayload, $webhookKey, true)
);
// Signature key from Xero request
$xeroSignatureKey = $_SERVER['HTTP_X_XERO_SIGNATURE'];
$isEqual = false;
if (hash_equals($computedSignatureKey, $xeroSignatureKey)) {
$isEqual = true;
http_response_code(200);
// getting and passing the data to the api functionality
$data = json_decode($rawPayload);
xero_api($data);
} else {
http_response_code(401);
}
///////////////////////////////////////////////////////////////////////////
// WEBHOOK AUTHENTICATION - END
///////////////////////////////////////////////////////////////////////////
?>
Api code:
<?php
///////////////////////////////////////////////////////////////////////////
// XERO API FUNCITONALITY - START
///////////////////////////////////////////////////////////////////////////
function xero_api($data) {
if ($data->events[0]->eventType === 'CREATE') {
$resourseId = $data->events[0]->resourceId;
///////////////////////////////////////////////////////////////////////////
// GET XERO CREDENTIALS - START
///////////////////////////////////////////////////////////////////////////
global $wpdb;
$xeroKeys = $wpdb->get_row("SELECT * FROM {$wpdb->prefix}xero_keys WHERE ID = 0", ARRAY_A);
$clientId = $xeroKeys['client_id'];
$clientSecret = $xeroKeys['client_secret'];
$refreshToken = $xeroKeys['refresh_token'];
$tenantId = $xeroKeys['tenant_id'];
///////////////////////////////////////////////////////////////////////////
// GET XERO CREDENTIALS - END
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
// GET ACCESS TOKEN AND GENERATE NEW REFRESH TOKEN - START
///////////////////////////////////////////////////////////////////////////
$args = array(
'headers' => array(
'grant_type' => 'refresh_token',
),
'body' => array(
'grant_type' => 'refresh_token',
'refresh_token' => $refreshToken,
'client_id' => $clientId,
'client_secret' => $clientSecret
)
);
$refreshTokenRes = wp_remote_post('https://identity.xero.com/connect/token?=', $args);
$refreshTokenBody = json_decode($refreshTokenRes['body']);
if (isset($refreshTokenBody->refresh_token) && isset($refreshTokenBody->access_token)) {
$updateTokens = $wpdb->update(
$wpdb->prefix . 'xero_keys',
array(
'refresh_token' => $refreshTokenBody->refresh_token,
'access_token' => $refreshTokenBody->access_token
),
array('ID' => 0),
array('%s', '%s'),
array('%d')
);
}
///////////////////////////////////////////////////////////////////////////
// GET ACCESS TOKEN AND GENERATE NEW REFRESH TOKEN - End
///////////////////////////////////////////////////////////////////////////
$args = array(
'headers' => array(
'xero-tenant-id' => $tenantId,
'Authorization' => 'Bearer ' . $refreshTokenBody->access_token,
'Accept' => 'application/json',
'Content-Type' => 'application/json'
),
);
$response = wp_remote_post('https://api.xero.com/api.xro/2.0/Invoices/' . $resourseId . '/Email', $args);
}
}
///////////////////////////////////////////////////////////////////////////
// XERO API FUNCITONALITY - END
///////////////////////////////////////////////////////////////////////////
?>
The problem is that you need to separate these calls.
As the comment to your question said, you need to first deal with the webhook, then queue the job that will trigger your API. Something like this would do
<?php
///////////////////////////////////////////////////////////////////////////
// WEBHOOK AUTHENTICATION - START
///////////////////////////////////////////////////////////////////////////
//hook section
$rawPayload = file_get_contents("php://input");
// Update your webhooks key here
$webhookKey = 'myWebhookKey';
// Compute the payload with HMACSHA256 with base64 encoding
$computedSignatureKey = base64_encode(
hash_hmac('sha256', $rawPayload, $webhookKey, true)
);
// Signature key from Xero request
$xeroSignatureKey = $_SERVER['HTTP_X_XERO_SIGNATURE'];
$isEqual = false;
if (hash_equals($computedSignatureKey, $xeroSignatureKey)) {
$isEqual = true;
http_response_code(200);
// getting and passing the data to the api functionality
$data = json_decode($rawPayload);
wp_schedule_single_event(
time() + 10,
'send_xero_api_call',
['data' => $data]
);
} else {
http_response_code(401);
}
///////////////////////////////////////////////////////////////////////////
// WEBHOOK AUTHENTICATION - END
///////////////////////////////////////////////////////////////////////////
Then you need to register a WP Cron
add_action('send_xero_api_call', 'xero_api_cron', 10);
function xero_api_cron($data) {
if ($data->events[0]->eventType === 'CREATE') {
$resourseId = $data->events[0]->resourceId;
///////////////////////////////////////////////////////////////////////////
// GET XERO CREDENTIALS - START
///////////////////////////////////////////////////////////////////////////
global $wpdb;
$xeroKeys = $wpdb->get_row("SELECT * FROM {$wpdb->prefix}xero_keys WHERE ID = 0", ARRAY_A);
$clientId = $xeroKeys['client_id'];
$clientSecret = $xeroKeys['client_secret'];
$refreshToken = $xeroKeys['refresh_token'];
$tenantId = $xeroKeys['tenant_id'];
///////////////////////////////////////////////////////////////////////////
// GET XERO CREDENTIALS - END
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
// GET ACCESS TOKEN AND GENERATE NEW REFRESH TOKEN - START
///////////////////////////////////////////////////////////////////////////
$args = array(
'headers' => array(
'grant_type' => 'refresh_token',
),
'body' => array(
'grant_type' => 'refresh_token',
'refresh_token' => $refreshToken,
'client_id' => $clientId,
'client_secret' => $clientSecret
)
);
$refreshTokenRes = wp_remote_post('https://identity.xero.com/connect/token?=', $args);
$refreshTokenBody = json_decode($refreshTokenRes['body']);
if (isset($refreshTokenBody->refresh_token) && isset($refreshTokenBody->access_token)) {
$updateTokens = $wpdb->update(
$wpdb->prefix . 'xero_keys',
array(
'refresh_token' => $refreshTokenBody->refresh_token,
'access_token' => $refreshTokenBody->access_token
),
array('ID' => 0),
array('%s', '%s'),
array('%d')
);
}
///////////////////////////////////////////////////////////////////////////
// GET ACCESS TOKEN AND GENERATE NEW REFRESH TOKEN - End
///////////////////////////////////////////////////////////////////////////
$args = array(
'headers' => array(
'xero-tenant-id' => $tenantId,
'Authorization' => 'Bearer ' . $refreshTokenBody->access_token,
'Accept' => 'application/json',
'Content-Type' => 'application/json'
),
);
$response = wp_remote_post('https://api.xero.com/api.xro/2.0/Invoices/' . $resourseId . '/Email', $args);
}
}
Something to this extent. The $data argument should contain the data you passed (not 100% sure how it looks for you). Also, you'll need to check the throttling by the API, so you might adjust the time of the execution of your job. And make sure you have somewhere stored if your background job finished successfully.
One thing I suggest, since you are storing sensitive info in your DB (access and refresh tokens), is to encrypt them when storing them in the DB, and decrypt when fetching them.
You can check how I implemented background jobs in my plugin
https://github.com/dingo-d/woo-solo-api/tree/develop/src/BackgroundJobs https://github.com/dingo-d/woo-solo-api/blob/develop/src/Request/SoloApiRequest.php#L428-L438
You can use WP Queue lib from deliciousbrains: https://github.com/deliciousbrains/wp-queue/
It's a wrapper around WP Cron with custom DB tables for handling the queues, which will allow you to check if the jobs executed correctly or not.
Hey, thanks for the response. I will try this out tomorrow and let you know how it goes.
This works perfectly without interfering with my webhook thanks!!! I just need to work on my api code a little and all should be good
Awesome, glad I could help :)
I think you missed defining the amount of parameters the function can have inside the add_action. I tried to edit but the edit is too small to be accepted
It depends on how many parameters you want to pass. For instance here, I am spreading the
$args
argument. I passed the 5 arguments here. You can do the same in your call. In the example I passed just one.