eBay Marketplace Account Deletion/Closure Notifications
Asked Answered
I

5

7

I am trying to start an eBay API in Python and I can't find a single answer as to how to get an API key with eBay's new requirements of "Account Deletion/Closure Notifications." Here's the link: https://developer.ebay.com/marketplace-account-deletion

Specifically, I am told that "Your Keyset is currently disabled" because I have not completed whatever process is needed for this marketplace account deletion/closure notification.

The problems?

  1. I have no idea if I need this.
  2. I have no idea how to actually do this.

Re: 1. It looks like this is for anyone who stores user data. I don’t think that’s me intentionally because I really just want to get sold data and current listings, but is it actually me?

Re: 2. I don’t understand how to validate it and send back the proper responses. I’ve gotten quite good at python but I’m lost here.

eBay forums are completely useless and I see no one with an answer to this. Any help is greatly appreciated.

Inefficiency answered 29/7, 2021 at 3:41 Comment(0)
A
3

Re: 1. Same. Here's my interpretation: In order to use their APIs, you need to provide (and configure) your own API, so they can communicate with you —programatically— and tell you what users have asked to have their accounts/data deleted.

Re: 2. To handle their GET and POST requests, I guess you'll need to configure a website's URL as an API endpoint. In Django, I might use something like this (untested) code:

import hashlib
import json
from django.http import (
    HttpResponse,
    JsonResponse,
    HttpResponseBadRequest
)


def your_api_endpoint(request):
    """
    API Endpoint to handle the verification's challenge code and
    receive eBay's Marketplace Account Deletion/Closure Notifications.
    """
    # STEP 1: Handle verification's challenge code
    challengeCode = request.GET.get('challenge_code')
    if challengeCode is not None:
       # Token needs to be 32-80 characters long 
       verificationToken = "your-token-012345678901234567890123456789"
       # URL needs to use HTTPS protocol
       endpoint_url = "https://your-domain.com/your-endpoint"
       # Hash elements need to be ordered as follows
       m = hashlib.sha256((challengeCode+verificationToken+endpoint_url).encode('utf-8'))
       # JSON field needs to be called challengeResponse
       return JsonResponse({"challengeResponse": m.hexdigest()}, status=200)
    
    # STEP 2: Handle account deletion/closure notification
    elif request.method == 'POST':
        notification_details = json.loads(request.body)
        # Verify notification is actually from eBay
        # ...
        # Delete/close account
        # ...
        # Acknowledge notification reception
        return HttpResponse(status=200)

    else:
        return HttpResponseBadRequest()

If you find the answer to question number one, please do let me know.

Adverb answered 7/8, 2021 at 23:45 Comment(1)
Pretty sure you need it if you store data ie. in your applications database or something, and you should be able to pretty easily opt out if you dont store data. Honestly im not sure how they enforce this, but personally to be on the safe side I would just implement it if you are storing data. I feel like the whole process of using the ebay api in this respect is pretty complicated for a layman and would turn a lot of people off, but what do I know.Haggis
H
3

Re: 1. You need to comply with eBay's Marketplace Account Deletion/Closure Notification workflow if you are storing user data into your own database. For example, using eBay's Buy APIs, you may get access to what users are selling on eBay (for ex. an eBay feed of products). If those eBay sellers decide they want to remove all of their personal data from eBay's database, eBay is requesting you remove their data from your database as well. If you are NOT storing any eBay user data into your database, you do not need to comply. Here is where you can find more info: https://partnerhelp.ebay.com/helpcenter/s/article/Complying-with-the-eBay-Marketplace-Account-Deletion-Closure-Notification-workflow?language=en_US

Re: 2. To be honest I've spent days trying to figure this out in Python (Django), but I have a solution now and am happy to share it with whoever else comes across this issue. Here's my solution:

import os
import json
import base64
import hashlib
import requests
import logging
from OpenSSL import crypto
from rest_framework import status
from rest_framework.views import APIView
from django.http import JsonResponse

logger = logging.getLogger(__name__)


class EbayMarketplaceAccountDeletion(APIView):
    """
    This is required as per eBay Marketplace Account Deletion Requirements.
    See documentation here: https://developer.ebay.com/marketplace-account-deletion
    """

    # Ebay Config Values
    CHALLENGE_CODE = 'challenge_code'
    VERIFICATION_TOKEN = os.environ.get('VERIFICATION_TOKEN')
    # ^ NOTE: You can make this value up so long as it is between 32-80 characters.
    ENDPOINT = 'https://example.com/ebay_marketplace_account_deletion'
    # ^ NOTE: Replace this with your own endpoint
    X_EBAY_SIGNATURE = 'X-Ebay-Signature'
    EBAY_BASE64_AUTHORIZATION_TOKEN = os.environ.get('EBAY_BASE64_AUTHORIZATION_TOKEN')
    # ^ NOTE: Here's how you can get your EBAY_BASE64_AUTHORIZATION_TOKEN:
    # import base64
    # base64.b64encode(b'{CLIENT_ID}:{CLIENT_SECRET}')

    def __init__(self):
        super(EbayMarketplaceAccountDeletion, self).__init__()

    def get(self, request):
        """
        Get challenge code and return challengeResponse: challengeCode + verificationToken + endpoint
        :return: Response
        """
        challenge_code = request.GET.get(self.CHALLENGE_CODE)
        challenge_response = hashlib.sha256(challenge_code.encode('utf-8') +
                                            self.VERIFICATION_TOKEN.encode('utf-8') +
                                            self.ENDPOINT.encode('utf-8'))
        response_parameters = {
            "challengeResponse": challenge_response.hexdigest()
        }
        return JsonResponse(response_parameters, status=status.HTTP_200_OK)

    def post(self, request):
        """
        Return 200 status code and remove from db.
        See how to validate the notification here:
        https://developer.ebay.com/api-docs/commerce/notification/overview.html#use
        """
        # Verify notification is actually from eBay #
        # 1. Use a Base64 function to decode the X-EBAY-SIGNATURE header and retrieve the public key ID and signature
        x_ebay_signature = request.headers[self.X_EBAY_SIGNATURE]
        x_ebay_signature_decoded = json.loads(base64.b64decode(x_ebay_signature).decode('utf-8'))
        kid = x_ebay_signature_decoded['kid']
        signature = x_ebay_signature_decoded['signature']

        # 2. Call the getPublicKey Notification API method, passing in the public key ID ("kid") retrieved from the
        # decoded signature header. Documentation on getPublicKey:
        # https://developer.ebay.com/api-docs/commerce/notification/resources/public_key/methods/getPublicKey
        public_key = None
        try:
            ebay_verification_url = f'https://api.ebay.com/commerce/notification/v1/public_key/{kid}'
            oauth_access_token = self.get_oauth_token()
            headers = {
                'Authorization': f'Bearer {oauth_access_token}'
            }
            public_key_request = requests.get(url=ebay_verification_url, headers=headers, data={})
            if public_key_request.status_code == 200:
                public_key_response = public_key_request.json()
                public_key = public_key_response['key']
        except Exception as e:
            message_title = "Ebay Marketplace Account Deletion: Error calling getPublicKey Notfication API."
            logger.error(f"{message_title} Error: {e}")
            return JsonResponse({}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

        # 3. Initialize the cryptographic library to perform the verification with the public key that is returned from
        # the getPublicKey method. If the signature verification fails, an HTTP status of 412 Precondition Failed is returned.
        pkey = crypto.load_publickey(crypto.FILETYPE_PEM, self.get_public_key_into_proper_format(public_key))
        certification = crypto.X509()
        certification.set_pubkey(pkey)
        notification_payload = request.body
        signature_decoded = base64.b64decode(signature)
        try:
            crypto.verify(certification, signature_decoded, notification_payload, 'sha1')
        except crypto.Error as e:
            message_title = f"Ebay Marketplace Account Deletion: Signature Invalid. " \
                            f"The signature is invalid or there is a problem verifying the signature. "
            logger.warning(f"{message_title} Error: {e}")
            return JsonResponse({}, status=status.HTTP_412_PRECONDITION_FAILED)
        except Exception as e:
            message_title = f"Ebay Marketplace Account Deletion: Error performing cryptographic validation."
            logger.error(f"{message_title} Error: {e}")
            return JsonResponse({}, status=status.HTTP_412_PRECONDITION_FAILED)

        # Take appropriate action to delete the user data. Deletion should be done in a manner such that even the
        # highest system privilege cannot reverse the deletion #
        # TODO: Replace with your own data removal here

        # Acknowledge notification reception
        return JsonResponse({}, status=status.HTTP_200_OK)

    def get_oauth_token(self):
        """
        Returns the OAuth Token from eBay which can be used for making other API requests such as getPublicKey
        """
        url = 'https://api.ebay.com/identity/v1/oauth2/token'
        headers = {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Authorization': f"Basic {self.EBAY_BASE64_AUTHORIZATION_TOKEN}"
        }
        payload = 'grant_type=client_credentials&scope=https%3A%2F%2Fapi.ebay.com%2Foauth%2Fapi_scope'
        request = requests.post(url=url, headers=headers, data=payload)
        data = request.json()
        return data['access_token']

    @staticmethod
    def get_public_key_into_proper_format(public_key):
        """
        Public key needs to have \n in places to be properly assessed by crypto library.
        """
        return public_key[:26] + '\n' + public_key[26:-24] + '\n' + public_key[-24:]
Huebner answered 19/4, 2022 at 20:56 Comment(0)
C
0

This is how I am dealing with the ebay notification requirement using Python3 cgi. Because bytes are sent, cannot use cgi.FieldStorage()

import os
import sys
import hashlib
import json
from datetime import datetime
from html import escape

import cgi
import cgitb

import io

include_path = '/var/domain_name/www'

sys.path.insert(0, include_path)

cgitb.enable(display=0, logdir=f"""{include_path}/tmp_errors""") # include_path is OUTDIR

dt_now = datetime.now()
current_dt_now = dt_now.strftime("%Y-%m-%d_%H-%M-%S")


def enc_print(string='', encoding='utf8'):
    sys.stdout.buffer.write(string.encode(encoding) + b'\n')


html = ''

challengeCode = ''


# GET
myQuery = os.environ.get('QUERY_STRING')
if myQuery.find('=') != -1: 
    pos = myQuery.find('=')
    
    var_name = myQuery[:pos]
    var_val = myQuery[pos+1:]
    
    challengeCode = var_val


# POST  
if os.environ.get('CONTENT_LENGTH') != None:
    totalBytes=int(os.environ.get('CONTENT_LENGTH'))
    reqbytes=io.open(sys.stdin.fileno(),"rb").read(totalBytes)


if challengeCode != '' :
    """
    API Endpoint to handle the verification's challenge code and
    receive eBay's Marketplace Account Deletion/Closure Notifications.
    """
    # STEP 1: Handle verification's challenge code

    # Token needs to be 32-80 characters long 
    verificationToken = "0123456789012345678901234567890123456789" #sample token
    # URL needs to use HTTPS protocol
    endpoint = "https://domain_name.com/ebay/notification.py" # sample endpoint
    # Hash elements need to be ordered as follows
    m = hashlib.sha256( (challengeCode+verificationToken+endpoint).encode('utf-8') )
    # JSON field needs to be called challengeResponse
    
    enc_print("Content-Type: application/json")
    enc_print("Status: 200 OK")
    enc_print()
    enc_print('{"challengeResponse":"' + m.hexdigest() + '"}')
    exit()
    
else :
    
    #html += 'var length:' + str(totalBytes) + '\n'
    html += reqbytes.decode('utf-8') + '\n'


    # STEP 2: Handle account deletion/closure notification

    # Verify notification is actually from eBay
    # ...
    # Delete/close account
    # ...
    # Acknowledge notification reception
    
    with open( f"""./notifications/{current_dt_now}_user_notification.txt""", 'w') as f:
        f.write(html)

    enc_print("Content-Type: application/json")
    enc_print("Status: 200 OK")
    enc_print()
    exit()
Combust answered 16/8, 2021 at 22:57 Comment(3)
I've been trying to get this to work with PHP for a week. It may be time to give up. I know nothing about python though. Does this actually work? Can I save it as a .py file online and use it as my endpoint? I'm guessing it's not quite that easy. Any help you can give would be wonderful.Trantham
I found this guide on the eBay fourms, which could be helpful for php usage. forums.developer.ebay.com/questions/43122/… . Although, I'm having an issue where I need my eBay Client Secret to use it, and I haven't been able to find any documentation on where to get this. In my primary eBay application keys page, it says "Your keyset is currently disabled, complete with Marketplace deletion/account closure notification process or apply for an exemption".Hoffarth
@AnthonyKnisely The link is deadHoyden
H
0

I've been trying @José Matías Arévalo code. It works except "STEP 2" branch - Django returns 403 error. This is because of by default Django uses CSRF middleware (Cross Site Request Forgery protection). To avoid 403 error we need to marks a view as being exempt from the protection as described here https://docs.djangoproject.com/en/dev/ref/csrf/#utilities so add couple strings in code:

from django.views.decorators.csrf import csrf_exempt    

@csrf_exempt
def your_api_endpoint(request):

And in my case I use url "https://your-domain.com/your-endpoint/" with slash symbol "/" at the end of url. Without this slash eBay doesn't confirm subscription.

Harbourage answered 20/10, 2021 at 9:43 Comment(0)
P
0

I am using Flask and this is the code I have used:

from flask import Flask, request
import hashlib

# Create a random verification token, it needs to be 32-80 characters long
verification_token = 'a94cbd68e463cb9780e2008b1f61986110a5fd0ff8b99c9cba15f1f802ad65f9'
endpoint_url = 'https://dev.example.com'

app = Flask(__name__)

# There will be errors if you just use '/' as the route as it will redirect eBays request
# eBay will send a request to https://dev.example.com?challenge_code=123
# The request will get redirected by Flask to https://dev.example.com/?challenge_code=123 which eBay will not accept
endpoint = endpoint_url + '/test'

# The Content-Type header will be added automatically by Flask as 'application/json'
@app.route('/test')
def test():
    code = request.args.get('challenge_code')
    print('Requests argument:', code)

    code = code + token + endpoint
    code = code.encode('utf-8')
    code = hashlib.sha256(code)
    code = code.hexdigest()
    print('Hexdigest:', code)

    final = {"challengeResponse": code}
    return final

## To run locally first use this:
# app.run(port=29)
Periderm answered 20/12, 2021 at 14:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.