I am trying to send vacancies to the Google Indexing API whenever we receive them from our vacancy provider. However, despite having set all the appropriate permissions, I keep receiving 403 status codes.
I have followed the "Prerequisites for the Indexing API" guide and created a Service account. In my case, I have 4 entries for the domain in Google Search Console, so I followed MarcQuay's answer and added the service account to all 4 entries.
I use the following code to implement the Google API Client and make my calls. The method sendGoogleRequest() is called for every vacancy that we receive.
// Google API setup
function sendGoogleRequest(array $aData, int $sStatus = 0)
{
global $DOMAIN, $GOOGLE_AUTH_CONFIG, $GOOGLE_AUTH_SCOPE, $GOOGLE_API_ENDPOINT;
$googleClient = new Google_Client();
$googleClient->useApplicationDefaultCredentials();
$googleClient->addScope([$GOOGLE_AUTH_SCOPE]);
// Update Google Indexing API. This will notify Google that the URL should be crawled again.
$httpClient = $googleClient->authorize();
$endpoint = $GOOGLE_API_ENDPOINT;
$sJobUrl = $DOMAIN . '/vacancies/' . $aData['url'];
$sType = "";
if (!empty($sStatus)) {
switch ($sStatus) {
case 1:
$sType = "URL_UPDATED";
break;
case 2:
$sType = "URL_DELETED";
break;
}
}
$content = "{
\"url\": \"$sJobUrl\",
\"type\": \"$sType\"
}";
$response = $httpClient->post($endpoint, ['body' => $content]);
$status_code = $response->getStatusCode();
return $status_code;
}
I have tried debugging it and it seems that '$credentials' is empty in $googleClient->authorize()
$authHandler = $this->getAuthHandler();
if ($credentials) {
$callback = $this->config['token_callback'];
$http = $authHandler->attachCredentials($http, $credentials, $callback);
} elseif ($token) {
$http = $authHandler->attachToken($http, $token, (array) $scopes);
} elseif ($key = $this->config['developer_key']) {
$http = $authHandler->attachKey($http, $key);
}
return $http;
However I have no idea what could be the cause of this.
Using this code returns a '403' for every call. After having browsed the internet for quite some time now, I can seem to find a definite answer, except for 'make sure the service account is an owner' but as previously stated, this is already the case.
I hope someone can help me out, since I have been stuck on this for longer than I'd like to admit.
If any more information is required I'll gladly update my answer.
EDIT: As per request, here is the full error message return by the $httpClient->post() call.
Response {#268 ▼
-reasonPhrase: "Forbidden"
-statusCode: 403
-headers: array:11 [▼
"Vary" => array:3 [▼
0 => "X-Origin"
1 => "Referer"
2 => "Origin,Accept-Encoding"
]
"Content-Type" => array:1 [▼
0 => "application/json; charset=UTF-8"
]
"Date" => array:1 [▼
0 => "Tue, 24 Sep 2019 11:25:29 GMT"
]
"Server" => array:1 [▼
0 => "ESF"
]
"Cache-Control" => array:1 [▼
0 => "private"
]
"X-XSS-Protection" => array:1 [▼
0 => "0"
]
"X-Frame-Options" => array:1 [▼
0 => "SAMEORIGIN"
]
"X-Content-Type-Options" => array:1 [▼
0 => "nosniff"
]
"Alt-Svc" => array:1 [▼
0 => "quic=":443"; ma=2592000; v="46,43,39""
]
"Accept-Ranges" => array:1 [▼
0 => "none"
]
"Transfer-Encoding" => array:1 [▼
0 => "chunked"
]
]
-headerNames: array:11 [▼
"vary" => "Vary"
"content-type" => "Content-Type"
"date" => "Date"
"server" => "Server"
"cache-control" => "Cache-Control"
"x-xss-protection" => "X-XSS-Protection"
"x-frame-options" => "X-Frame-Options"
"x-content-type-options" => "X-Content-Type-Options"
"alt-svc" => "Alt-Svc"
"accept-ranges" => "Accept-Ranges"
"transfer-encoding" => "Transfer-Encoding"
]
-protocol: "1.1"
-stream: Stream {#256 ▼
-stream: stream resource @123 ▼
wrapper_type: "PHP"
stream_type: "TEMP"
mode: "w+b"
unread_bytes: 0
seekable: true
uri: "php://temp"
options: []
}
-size: null
-seekable: true
-readable: true
-writable: true
-uri: "php://temp"
-customMetadata: []
}
}
I ended up fixing this issue by using a Service account as AuthConfig
// Initialize Google_Client to submit vacancies to Indexing API
$googleClient = new Google_Client();
$googleClient->setAuthConfig($GOOGLE_AUTH_CONFIG);
$googleClient->addScope([$GOOGLE_AUTH_SCOPE]);
$googleClient->setAccessType("offline");
$googleClient is used for every call
function sendGoogleRequest(array $aData, int $sStatus = 0, Google_Client $google_Client) {
global $DOMAIN;
$sJobUrl = 'https://MY-DOMAIN.com/' . $aData['url'];
$sType = "";
if (!empty($sStatus)) {
switch ($sStatus) {
case 1:
$sType = "URL_UPDATED";
break;
case 2:
$sType = "URL_DELETED";
break;
}
}
$urlNotification = new Google_Service_Indexing_UrlNotification();
$urlNotification->setType($sType);
$urlNotification->setUrl($sJobUrl);
$indexingService = new Google_Service_Indexing($google_Client);
$response = $indexingService->urlNotifications->publish($urlNotification);
return $response;
}
This script is then called once per day and posts every URL to the Indexing API. To not exceed the limit of 250 requests per day, make sure you exclude vacancies that should no longer be indexed.