question

swappart avatar image
8 Likes"
swappart asked

Working code: Marketplace Account Deletion Notifications Handler

Below is working code which will allow you to subscribe to Marketplace Account Deletion Notifications and process the notifications as they are received. Copy and paste below code to file. Save it as a PHP file with whatever name you want. Example: notify.php. Upload to your server and then set the endpoint and verification token in your developer account. Should work for PHP version 5 and above.



<?php

##########################################################
### Marketplace Account Deletion Notifications Handler ###
### By Swappart 2021                                   ###
##########################################################

### Sign into eBay Developers Program and find your Client ID and Client Secret under 
### application access keys.
### Your endpoint and verificationToken are values you specify when subscribing to 
### Marketplace Account Deletion Notifications in your developer account.
### The endpoint URL must not contain the word eBay.
### The verification token has to be between 32 and 80 characters, and allowed 
### characters include alphanumeric characters, underscore (_),  and hyphen (-).
### No other characters are allowed. Example: 654321abcdef654321abcefd123456fe;

$client_id =  "Your client ID goes here";  //Also known as App ID.
$client_secret =  "Your client secret goes here"; //Also known as Cert ID.
$verificationToken = "Your verification token goes here";
$endpoint = "YourEndpointURL";

// Sets base file location to one level above webroot. You can
// change this if you want to use a specific location.
$fileStorageLocation = realpath(dirname(__FILE__) . '/..');


 
#####################################
### This part validates endpoint. ###
#####################################
if(isset($_GET['challenge_code'])){
$challengeCode = $_GET['challenge_code'];
header('Content-Type: application/json'); 
$d=$challengeCode.$verificationToken.$endpoint; 
$hd=array("challengeResponse"=>hash("sha256", $d));
 
echo(json_encode($hd));
}

 
$json = file_get_contents('php://input');
$message = json_decode($json, true);

####################################################
### Creates file to help with debugging script   ###
### Find file debug.txt in the directory above   ###
### webroot. May contain sensitive info, so do   ###
### not post the contents if you have a problem. ###
####################################################
$config['Decoded JSON Message'] = $message;
$config['Server'] = $_SERVER;
$data = parseArray($config);
$debug = $fileStorageLocation . '/debug.txt';
write_to_file($debug, $data);




#######################################
### This part handles notfications. ###
#######################################
if(isset($_SERVER['HTTP_X_EBAY_SIGNATURE'])){
    
if (!$message) {
    throw new Exception('Invalid message');
}
 
if (empty($_SERVER['HTTP_X_EBAY_SIGNATURE'])) {
    throw new Exception('No signature passed');
}
 
$signature = json_decode(base64_decode($_SERVER['HTTP_X_EBAY_SIGNATURE']), true) ?: [];
if (empty($signature['kid'])) {
    throw new Exception('Signature not decoded');
}
 

 
$token = retrieveToken($client_id, $client_secret, $fileStorageLocation);
 
$ch = curl_init();
$fp = fopen($fileStorageLocation . '/curlLog.txt', 'w') or die('Unable to open file!');
curl_setopt($ch, CURLOPT_URL, "https://api.ebay.com/commerce/notification/v1/public_key/" . $signature['kid']);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type = application/json','Accept: application/json', 'Authorization:bearer ' . $token));
curl_setopt($ch, CURLOPT_FILE, $fp);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 
 
 
$response = curl_exec($ch);
 
 
 
$curl_errno = curl_errno($ch);
$curl_error = curl_error($ch);
 
if ($curl_errno > 0) {
    
   fwrite($fp, "cURL Error ($curl_errno): $curl_error\n");
        } else {
            fwrite($fp, "Data received: $response\n");
            
        }
 
$publicKey = json_decode($response, true);
 
curl_close($ch);
fclose($fp);
 
if (empty($publicKey['key'])) {
    throw new Exception(
        'getPublicKey response: ' . json_encode($publicKey) . ' for signature ' . $signature['kid']
    );
}
 
if ($publicKey['algorithm'] !== 'ECDSA' || $publicKey['digest'] !== 'SHA1') {
    throw new Exception('Unsupported encryption algorithm/digest');
}
 
if (preg_match('/^-----BEGIN PUBLIC KEY-----(.+)-----END PUBLIC KEY-----$/', $publicKey['key'], $matches)) {
    $key = "-----BEGIN PUBLIC KEY-----\n"
        . implode("\n", str_split($matches[1], 64))
        . "\n-----END PUBLIC KEY-----";
} else {
    throw new Exception('Invalid key');
}
 
 
$verificationResult = openssl_verify(
    json_encode($message),
    base64_decode($signature['signature']),
    $key,
    OPENSSL_ALGO_SHA1
);
 
 
 
if ($verificationResult === 1) {
    echo 'OK';
    
    // If you need to check to see if you have any data related to the deleted user
    // this is where you do it. You would pass the below username and/or userId 
    // variables to a script which could check your database, and then handle the
    // result accordingly.
    
    
      $username = $message['notification']['data']['username'];
      $userId = $message['notification']['data']['userId'];
      $eiasToken = $message['notification']['data']['eiasToken'];
    
    
    
} else {
$myfile = fopen($fileStorageLocation . '/verification-error.txt', 'w') or die('Unable to open file!');
$txt = "Verification Failed!";
fwrite($myfile, $txt);
fclose($myfile);
    throw new Exception('Verification failure', 412);
}
}
 
 
 
 
#############################################################
### This part returns stored OAuth token, or new token if ###
### stored one is expired. Tokens only valid for 2 hours. ###
#############################################################
function retrieveToken($client_id, $client_secret, $fileStorageLocation){
 $date = new DateTime();
 $date->getTimestamp();
 $auth = null;
 if(file_exists($fileStorageLocation . '/auth.ini')){
  $auth = parse_ini_file($fileStorageLocation . '/auth.ini', true);   
if(!isset($auth['time']) ||  !isset($auth['token'])){
    //ini file exists but doesn't contain token. We'll fetch one and update the file.
    $token = getNewToken($client_id, $client_secret, $fileStorageLocation);
}else{
   $s = $auth['time'];
   $t= new DateTime("@$s");
   
    if($date->getTimestamp() > $t->add(new DateInterval('PT7170S'))->getTimestamp()){
        //Token expired. We'll fetch a new one.
        $token = getNewToken($client_id, $client_secret, $fileStorageLocation);
    }else{
        //Stored token still good! Using it"
        $token = $auth['token'];
        
    }
}
 
 }else{
     //ini file doesn't exist yet. We'll fetch a token and create the file.
     $token = getNewToken($client_id, $client_secret, $fileStorageLocation); 
 }
 
 return $token;
 
}
 
 
####################################################
### This part fetches new OAuth token as needed. ###
####################################################
function getNewToken($client_id, $client_secret, $fileStorageLocation) {
 
$ch = curl_init();
 
curl_setopt($ch, CURLOPT_URL, 'https://api.ebay.com/identity/v1/oauth2/token');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, "grant_type=client_credentials&scope=https://api.ebay.com/oauth/api_scope");
 
$headers = array();
$headers[] = 'Content-Type: application/x-www-form-urlencoded';
$headers[] = 'Authorization: Basic ' . base64_encode($client_id . ':' . $client_secret);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
 
$result = curl_exec($ch);
if (curl_errno($ch)) {
    echo 'Error:' . curl_error($ch);
}
curl_close($ch);
$tk = json_decode($result, true);
$date = new DateTime();
$time = $date->getTimestamp();
 
 
// Update values
$config['time'] = $time;
$config['token'] = $tk['access_token'];
 
$data = parseArray($config);
 

// Write ini file values
$inifile = fopen($fileStorageLocation . '/auth.ini', 'w') or die('Unable to open file!');
write_to_file($fileStorageLocation . '/auth.ini', $data);
fclose($inifile);
    
  return $tk['access_token']; 
}
 
 
 
#######################################################################
### This part parses arrays.                                        ###
#######################################################################
function parseArray($array = []) {
       
        // check second argument is array
        if (!is_array($array)) {
            throw new \InvalidArgumentException('Function argument must be an array.');
        }
 
                              // process array
        $data = array();
        foreach ($array as $key1 => $val1) {
            if (is_array($val1)) {
                $data[] = "[$key1]";
                
                
                foreach ($val1 as $key2 => $val2) {
                    if (is_array($val2)) {
                        
                        foreach ($val2 as $key3 => $val3) {
                             if (is_array($val3)) {
                            foreach ($val3 as $key4 => $val4) {
                                
                                
                            if (is_numeric($key3)) {
                                $data[] = $key3.'[] = '.(is_numeric($val4) ? $val4 : (ctype_upper($val4) ? $val4 : '"'.$val4.'"'));
                            } else {
                                $data[] = $key2.'['.$key3.'] ['.$key4.']= '.(is_numeric($val4) ? $val4 : (ctype_upper($val4) ? $val4 : '"'.$val4.'"'));
                            }
                             
                            }
                            }else {
                                $data[] = $key2.'['.$key3.'] = '.(is_numeric($val3) ? $val3 : (ctype_upper($val3) ? $val3 : '"'.$val3.'"'));
                            }
                        }
                        
                        
                    } else {
                        $data[] = $key2.' = '.(is_numeric($val2) ? $val2 : (ctype_upper($val2) ? $val2 : '"'.$val2.'"'));
                    }
                }
                
                
                
            } else {
                $data[] = $key1.' = '.(is_numeric($val1) ? $val1 : (ctype_upper($val1) ? $val1 : '"'.$val1.'"'));
            }
            
            $data[] = null;
        }
 
      return $data;
    }
 
###########################################################
### This part writes arrays to file.                    ### 
### Used for storing OAuth token for later use.         ###
### Also used to create debug file.                     ###
###########################################################
function write_to_file($file, $dataArray = []) {
        
// check first argument is string
        if (!is_string($file)) {
            throw new \InvalidArgumentException('Function argument 1 must be a string.');
        }
 
        // check second argument is array
        if (!is_array($dataArray)) {
            throw new \InvalidArgumentException('Function argument 2 must be an array.');
        }
        // open file pointer, init flock options
        $fp = fopen($file, 'w');
        $retries = 0;
        $max_retries = 100;
 
        if (!$fp) {
            return false;
        }
 
        // loop until get lock, or reach max retries
        do {
            if ($retries > 0) {
                usleep(rand(1, 5000));
            }
            $retries += 1;
        } while (!flock($fp, LOCK_EX) && $retries <= $max_retries);
 
        // couldn't get the lock
        if ($retries == $max_retries) {
            return false;
        }
 
        // got lock, write data
        fwrite($fp, implode(PHP_EOL, $dataArray).PHP_EOL);
 
        // release lock
        flock($fp, LOCK_UN);
        fclose($fp);
 
        return true;
    }

    
?>
marketplace account deletion notification endpointmarketplace_account_deletionmarketplace notificationsmarketplace account deletion notifications
· 13
10 |600 characters needed characters left characters exceeded

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Thanks for sharing with us! It's very nice of you! Will test it soon.
1 Like 1 ·
You're welcome.
0 Likes 0 ·

Hi @swappart when i try to save endpoint it say "Marketplace account deletion endpoint validation failed " i have tried different urls but problem still exist. I have multiple application keys . I tried with almost all application keys. please help me thanks

0 Likes 0 ·

@swappart this is the best option I have seen for my purposes but I have been having problems getting past the reported error by dreamweaver. According to dreamweaver there is a syntax error here below and I cant find it.

$signature = json_decode(base64_decode($_SERVER['HTTP_X_EBAY_SIGNATURE']), true) ?: [];

Has anyone managed to get the whole script working properly yet?.


0 Likes 0 ·
I haven't used DreamWeaver in years, so I'm not sure what the issue is or how to resolve it. The code is correct though. Make sure you're validating against the correct PHP version. It should work for PHP 5 and above. Can you just ignore the error? For most people, the script works as written. When it doesn't, it's usually something to do with their server configuration.
0 Likes 0 ·

@swappart

addendum to my previous post: I have tried every conceivable way I know to obtain the signature. The signature header is not displayed in browsers due to security issues I read. I do know that the header is not empty because it writes the information to the debug.txt file. I am trying another solution to parse the debug text file when it ie written instead to get the signature values and pass them into a $variable. that section of code will probable have to be modified. I can then use the stored variable to decode the signature.

If anyone has any idea how to do it that will be great.


0 Likes 0 ·

I'm a bit confused as to what your actual issue is.

0 Likes 0 ·

I saw elsewhere where you said you made the following changes.

  • $json = file_get_contents('php://input');
  • $message = json_decode($json, true);

replaced with these two lines of code you found somewhere :

$inputJSON = file_get_contents('php://input');
$input = json_decode($inputJSON, true);

That doesn't do anything but change the variable names. It's completely redundant except for later in the code where the $message variable would now be undefined. That could certainly cause an issue.

0 Likes 0 ·

Additionally, a syntax error can be something as simple as a missing or extra curly bracket or semicolon. Your validator may indicate a particular line of code, but that doesn't always mean it's something with that line. It can also mean it expected something else, such as a curly bracket or semicolon, but instead found that particular line of code. I would suggest trying the code as it's written and seeing if you can get it to work, and then make any changes necessary to incorporate it into existing code after (assuming that's what you're trying to do).

0 Likes 0 ·
Show more comments
boddies avatar image
1 Like"
boddies answered

Hi - thanks for posting this!

SCRIPT_URI isn't defined on my server - should that just be the same as the endpoint provided to ebay on the test page?


· 3
10 |600 characters needed characters left characters exceeded

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Yes, you could just change it to endpoint = "your endpoint";

0 Likes 0 ·
Thanks - all working great here now.
1 Like 1 ·
I updated the original post to let others know how to resolve this issue if they have it too. Thanks for inquiring about it.
0 Likes 0 ·
cottage_collector avatar image
1 Like"
cottage_collector answered

Thanks for posting this, big help

· 1
10 |600 characters needed characters left characters exceeded

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

You're welcome.
0 Likes 0 ·
kuo420 avatar image
0 Likes"
kuo420 answered

Thanks for the script. I have tried and followed all but still getting the error: Marketplace account deletion endpoint validation failed. Click here to learn more about setting up an endpoint.

The endpoint url I assigned for the php script file it shows:

404 - File or directory not found.


Is there anything else need to be aware? Thank you again!


· 7
10 |600 characters needed characters left characters exceeded

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

For some reason, your script url isn't publically available. There are a lot of potential reasons for that. Do you have SSL set up?
0 Likes 0 ·

Yes I have the SSL setup. The domain and folder url runs with other php file no problem; not sure why the script url not working. I set it to: https://mydomain.com/endpoint.php

Thanks again for your time and help!

0 Likes 0 ·

Do you have any errors getting logged?

0 Likes 0 ·
Show more comments
feedmark avatar image
0 Likes"
feedmark answered

Hi guys,
Can you help me to decide this question?
We only have a scheduled console app by our inhouse server which logs in our ebay shop and downloading the orders and put them in a SQL table.
Do we need to subscribe to Marketplace Account Deletion Notifications?

Many thanks
Zoltan

· 1
10 |600 characters needed characters left characters exceeded

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

I also just have a console app which is only for my own use, but I still chose to subscribe to Marketplace Account Deletion Notifications. I already had a website, so it wasn't anything which would cost me money to do. Also, when I set my server up to receive the notifications, there wasn't an exclusion option, though they were talking about it being something they were considering. In my experience, I've found that eBay changes their policies too often, so maybe now an exclusion is OK, but who is to say that a few months from now they won't change their minds again?

0 Likes 0 ·
audio-link avatar image
0 Likes"
audio-link answered

Thank you! I struggled a bit but I have added some notes help others:

Endpoint should be a direct URL to the file with the script. Let's say that we've taken swappart's script and copied it to a file called 'notify.php' and our domain name is example.com

Then, set your endpoint:

$endpoint = "https://example.com/notify.php";


Make sure you are using the correct Sandbox or Production client id / secret which can be found here: https://developer.ebay.com/my/keys

You can set a $verificationToken yourself - it can be anything as long as it is 32-80 characters and follows the requirements that swappart has put in the comments.

Make sure that your endpoint can be accessed outside your network, is a HTTPS:// address with a valid SSL certificate.


If you get Error 500: Check your credentials as they likely are not correct.

· 1
10 |600 characters needed characters left characters exceeded

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Thank you.
1 Like 1 ·
po906188 avatar image
0 Likes"
po906188 answered

Thanks!

Just a little question, where did you see that?
I didn't see any mention of it anywhere

  1. ### The endpoint URL must not contain the word eBay.
· 1
10 |600 characters needed characters left characters exceeded

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

I don't know where it's written, but there's several other threads where people couldn't get an endpoint to work until they removed the word ebay from the address. eBay owns the rights to the word.
0 Likes 0 ·
5lkeg avatar image
0 Likes"
5lkeg answered

One Quick question, once successfully "send test notification" do you receive any respond back? I did not receive the "challenge code" respond though.

· 1
10 |600 characters needed characters left characters exceeded

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

When you send the test notification, you will get a notification at the top of the screen telling you if it was successful or not. Challenge code is only sent when verifying the endpoint address.
1 Like 1 ·
enveloppebulle avatar image
0 Likes"
enveloppebulle answered

THANK YOU SO MUCH !!!!!!!!!!!!!!!!!
perfect ! it's really, and finally, working ! :) :) :)

· 1
10 |600 characters needed characters left characters exceeded

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

You're welcome.
0 Likes 0 ·
elfe118 avatar image
0 Likes"
elfe118 answered

I copied the code, change variables, open the URL in my browser and got empty body in the response.
I added the variable "challenge_code" into URL and got response:

{"challengeResponse":"b0835dcfc5d9e6a6230fab0961e4ae0b3a********************"}

1. What I need to do after this?
2. Do I need somehow to proccess these responses?

· 6
10 |600 characters needed characters left characters exceeded

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

If you open the URL in your browser, you should get an empty page (unless you also send the variable). If you now set the endpoint(URL) and verification code in your developer account, eBay should be able to verify the endpoint is working. Then you would send the test notification and if everything is correct, you will see a notification at the top of the screen which says it was successful.
0 Likes 0 ·

How I can get the verification token? I use library https://github.com/davidtsadler/ebay-sdk-php for my website.

0 Likes 0 ·
You create the verification token and enter it into the script, and also your developer account in the space under where you enter your endpoint URL. You don't need any SDK for this.
0 Likes 0 ·
Show more comments
musical-progressions avatar image
0 Likes"
musical-progressions answered

I was able to verify my endpoint, but I get an error when sending the test message...Notification delivery failed with HTTP status code 400 from https://www.mydomain/closure.php. Please ensure that the marketplace account deletion notification endpoint is ready to receive notifications.

I was able to determine that the $_SERVER['HTTP_X_EBAY_SIGNATURE'] variable is not being set. Any idea why that might happen?

· 15
10 |600 characters needed characters left characters exceeded

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

I'm having similar errors except my status error code is 444.
0 Likes 0 ·
What PHP version are you using? Others have had issues which were resolved after updating their PHP version. I don't know what the minimum would be, but it doesn't seem to work with PHP 4.x, and does work with 7.x.
0 Likes 0 ·
Thanks, I thought of that too. I'm using 5.4. I'd hate to have to change just for this, but there might not be any other way.
0 Likes 0 ·
Show more comments
I went through the trouble of updating to PHP 7. I'm still getting the same error. Is it possible to get a better description of what's causing the error?
0 Likes 0 ·
I ran your functions seperately to confirm that it would create the auth.ini file with the token in it. That works fine. I really think I'm not receiving the http_x_ebay_signature for some reason.
0 Likes 0 ·
swappart avatar image swappart musical-progressions ·

This writes JSON, server details, and message headers to a file. Send notification, then look for test.txt above script directory. Don't post results, may contain sensitive info.

// Create debug file
$config['Decoded JSON Message'] = $message;
$config['Server'] = $_SERVER;
if(!file_exists('../test.txt')){
$testFile = fopen("../test.txt", "w") or die("Unable to open file!");
fclose($testFile);
// Write ini file values
write_ini_file('../test.txt', $config);
}else{
// write ini file
write_ini_file('../test.txt', $config);
}
0 Likes 0 ·
Show more comments

Write an Answer

Hint: Notify or tag a user in this post by typing @username.

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.