- 21 Minutes to read
- Print
- PDF
Sellers API Documentation
- 21 Minutes to read
- Print
- PDF
How to connect to Sellers API?
Documentation Version | 0.0.3 - link to swagger doc added 0.0.4 - Cancel webhook added, webhook statuses values changed: RESERVE -> BUYING, GIVE -> BOUGHT, (new) CANCELED 0.0.5 - deliveredwebhook added 0.0.6 - removed reserve and give names 0.0.7 - webhooks documentation added 0.0.9 - offer price calculator endpoint added 0.0.9 - download and remove stock endpoint added 0.0.10 - modified part how to login using curl 0.0.11 - described where to find client_secret and client_id 0.0.12 - events description added 0.0.13 - releasedStockIdand releasedExternalStockId added to webhook with state DELIVERED , updated examples 0.0.14 - popularityBid added to webhook, updated examples 0.0.15 - added information how use merchant panel to set up webhooks 0.0.16 - information about sandbox environment 0.0.17 - updated information about changed requests limits 0.0.18 - FAQ added 0.0.19 - Information how to manage wholesale tiers added 0.0.20 - webhooks documentation updated (new endpoints) 0.0.21 - add information about uploading images 0.0.22 - webhooks version2, new functionality webhook request re-try 0.0.23 - download and remove stock endpoint removed 0.0.24 - ability to declare a text stock, added reservationId field to uploaded stock 0.0.25 - added new offer position changed webhook 0.0.26 - Sandbox login update to CAS 0.0.27 - Temporary blocking of the offer 0.0.28 - Introducing Smart Pricing Assistant and Sales Booster 0.0.29 - Maximum Price for Smart Pricing Assistant 0.0.30 - Blocking and unblocking webhooks |
Sales Manager Version | 0.3.0 and above |
Requests limits:
2000 POST/PATCH per minute
4000 GET per minute
Documentation have production addresses. Before you run on production please test your integration on sandbox environment. To create sandbox account proceed with the same steps like for production. Needed links are in table below
Sandbox application is on same domain as production, so you can encounter problems with logging (sessions are stored in cookies). To solve this issue you have to clear you cookies or use other browser than you use for production environment.
Address | Production | Sandbox |
Swagger documentation | https://gateway.kinguin.net/sales-manager-api/api/swagger-ui.html | https://gateway.sandbox.kinguin.net/sales-manager-api/api/swagger-ui/index.html |
Gateway main domain | ||
Kinguin ID | https://sandbox.kinguin.net and click “Sign In” in the top-right corner to create account |
1. Requirements
To get access to API you have to have an account in Kinguin. Click “Sign In” in the top-right corner of the website to create an account.
Send a message to api@kinguin.io requesting access to Kinguin public API.
Example message:
Hello,
I would like to request access to Kinguin public API for Sellers:
Environment: production or sandbox
Kinguin account email: [your-kinguin-account@email.com]
Application name: [alias name for this access]
Declared Stock: yes or no
In a response you will receive your individual client ID and secret which will be need to retrieve authorization token as oAuth 2.0 client credentials flow
2. Authorization
A client credentials flow is used for oAuth 2.0 authorization. All authorized requests require passing a Bearer code.
To retrieve your bearer code you have to send a request with grant type client_credentials.
To be allow create proper request you have to have:
client_id which is Public ID
client_secret which is Secret ID.
Both values you will receive as a response for your grant access request
Acquiring an access token
curl https://id.kinguin.net/auth/token \
--header 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=client_credentials' \
-d 'client_id=${CLIENT_ID}' \
-d 'client_secret=${CLIENT_SECRET}'
response:
{
"access_token":"MWE5NjJiNjkzNzU2NTU5ZjVhNjUzOTk3MmExOGUxMThiYWQ3YjA0NzY1MzBkNDRkMTczOWYzOWY3MzEyYjI0Nw",
"expires_in":3600, # seconds
"token_type":"bearer",
"scope":null,
}
Token has expiration time and have to be acquire again before expiration
To acquire new / refresh access token (previous one has expired) repeat point 1.
3. Up Front stock upload
Bearer is needed. Take a look on Authorization for information on how to get it.
ProductId is needed. Take a look on the appendix for information on how to match a product or find productId by name or catalog_id.
Test productID: 5c9b71292539a4e8f1809707. Can be used on production environment and is available here
Price amount is in cents
To use this flow you have to create an offer and then upload Stock
Creating an offer
curl -X POST \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{"productId": "${PRODUCT_ID}", "price": {"amount": 2, "currency": "EUR"}}' \
https://gateway.kinguin.net/sales-manager-api/api/v1/offers
response:
{
"id":"${OFFER_ID}",
"productId":"${PRODUCT_ID}",
"name":"Testowe CD Key",
"sellerId":"xxx",
"status":"ACTIVE",
"block":null,
"priceIWTR":{
"amount":2, #cents
"currency":"EUR"
},
"declaredStock":0,
"reservedStock":0,
"availableStock":0,
"updatedAt":"2020-03-06T10:58:49.088+0000",
"createdAt":"2020-03-06T10:58:48.951+0000",
"buyableStock":0,
"wholesale":{
"name":"Default",
"enabled":true,
"tiers":[
{
"discount":0,
"level":1,
"price":{
"amount":2,
"currency":"EUR"
},
"priceIWTR":{
"amount":2,
"currency":"EUR"
}
},
{
"discount":0,
"level":2,
"price":{
"amount":2,
"currency":"EUR"
},
"priceIWTR":{
"amount":2,
"currency":"EUR"
}
},
{
"discount":0,
"level":3,
"price":{
"amount":2,
"currency":"EUR"
},
"priceIWTR":{
"amount":2,
"currency":"EUR"
}
},
{
"discount":0,
"level":4,
"price":{
"amount":2,
"currency":"EUR"
},
"priceIWTR":{
"amount":2,
"currency":"EUR"
}
}
]
}
}
Uploading stock
curl -X POST \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{"body": "cd key", "mimeType": "text/plain"}' \
https://gateway.kinguin.net/sales-manager-api/api/v1/offers/${OFFER_ID}/stock
# to upload image send payload where body is base64 string
# '{"body": "[image-base64]", "mimeType": "image/jpeg"}'
response:
{
"id":"f64e66d5-e26c-426a-ba8b-cfeb220972ff",
"productId":"${PRODUCT_ID}",
"offerId":"${OFFER_ID}",
"sellerId":5005408,
"status":"AVAILABLE"
}
4. Declared Stock approach integration
Bearer is needed. Take a look on Authorization for information on how to get it.
ProductId is needed. Take a look on the appendix for information on how to match a product or find productId by name or catalog_id.
Test productID: 5c9b71292539a4e8f1809707. Can be used on production environment and is available here
Price amount is in cents
To have access to using this approach you have to get privilege. Please send a message to api@kinguin.io with a request for Declared Stock approach activation.
To support this flow you have to set webhooks addresses by sending a request,
curl -i -X POST \
https://gateway.kinguin.net/envoy/api/v1/subscription \
-H 'authorization: Bearer ${BEARER}' \
-H 'content-type: application/json' \
-d '{
"endpoints": {
"reserve": "https://webhook.site/6e8ab695-872b-46a2-affe-218ac3b35c5c",
"give": "https://webhook.site/6e8ab695-872b-46a2-affe-218ac3b35c5c",
"cancel": "https://webhook.site/6e8ab695-872b-46a2-affe-218ac3b35c5c",
"delivered": "https://webhook.site/6e8ab695-872b-46a2-affe-218ac3b35c5c",
"outofstock": "https://webhook.site/6e8ab695-872b-46a2-affe-218ac3b35c5c",
"returned": "https://webhook.site/6e8ab695-872b-46a2-affe-218ac3b35c5c",
"reversed": "https://webhook.site/6e8ab695-872b-46a2-affe-218ac3b35c5c",
"refunded": "https://webhook.site/6e8ab695-872b-46a2-affe-218ac3b35c5c",
"processingpreorder": "https://webhook.site/6e8ab695-872b-46a2-affe-218ac3b35c5c"
},
"headers": [{"name":"X-Auth-Token", "value":"xxxx"}]
}'
We suggest you set up the header with token to validate requests from Kinguin
more info about webhooks and event statuses in section - Appendix: Events
or using your Merchant Panel where you can set webhooks.
Due to design we are not able to provide order of events, e.g. network delays. That why subscriber system should be proof against “invalid” order of events.
1. Create an offer or with declared stock or update existing one
curl -i -X PATCH \
-H "Authorization: Bearer ${BEARER}" \
-H "Content-Type: application/json" \
-d '{"declaredStock": 1, "declaredTextStock": 1}' \
https://gateway.kinguin.net/sales-manager-api/api/v1/offers/${OFFER_ID}
Field declaredTextStock is optional. The field means how much of the stock set in the declared stock is of the text type. More in the section “4.1 declared text stock”
2. Listen on reservation event (BUYING)
{
"name": "Testowe CD Key",
"price": {
"amount":2,
"currency":"EUR"
},
"priceIWTR": {
"amount":2,
"currency":"EUR"
},
"commissionRule": {"fixedAmount":0,"percentValue":0.0,"ruleName":"Zero"},
"productId": "${PRODUCT_ID}",
"offerId": "${OFFER_ID}",
"status": "BUYING",
"reservationId": "8a9de902-ac4b-4531-b207-cf9e74aaa2b8",
"availableStock": 0,
"buyableStock": 0,
"declaredStock": 1,
"reservedStock": 1,
"requestedKeyType": null,
"updatedAt": "2020-03-06T15:58:49.088+0000",
"popularityBid":{
"amount": 0,
"currency": "EUR"
}
}
3. Listen on give event (BOUGHT)
Depending on the specifics of your system you may need to make sure that BUYING event came before the BOUGHT event. In order to achieve this, you should respond with 4xx HTTP status if you received BOUGHT event for specific reservationId before BUYING event. It will result in retrying to send the BOUGHT event for as long as you decide you have information from BUYING event. Then you should respond with 2xx HTTP status in order to accept this information.
{
"name": "Testowe CD Key",
"price": {
"amount":2,
"currency":"EUR"
},
"priceIWTR": {
"amount":2,
"currency":"EUR"
},
"commissionRule": {"fixedAmount":0,"percentValue":0.0,"ruleName":"Zero"},
"productId": "${PRODUCT_ID}",
"offerId": "${OFFER_ID}",
"status": "BOUGHT",
"reservationId": "8a9de902-ac4b-4531-b207-cf9e74aaa2b8",
"availableStock": 0,
"buyableStock": 0,
"declaredStock": 1,
"reservedStock": 1,
"requestedKeyType": null,
"updatedAt": "2020-03-06T16:00:12.088+0000",
"popularityBid":{
"amount": 0,
"currency": "EUR"
}
}
4. Listen on cancel event
{
"name": "Testowe CD Key",
"price": {
"amount":2,
"currency":"EUR"
},
"priceIWTR": {
"amount":2,
"currency":"EUR"
},
"commissionRule": {"fixedAmount":0,"percentValue":0.0,"ruleName":"Zero"},
"productId": "${PRODUCT_ID}",
"offerId": "${OFFER_ID}",
"status": "CANCELED",
"reservationId": "8a9de902-ac4b-4531-b207-cf9e74aaa2b8",
"availableStock": 0,
"buyableStock": 1,
"declaredStock": 1,
"reservedStock": 0,
"requestedKeyType": null,
"updatedAt": "2020-03-06T16:00:12.088+0000",
"popularityBid":{
"amount": 0,
"currency": "EUR"
}
}
5. Upload stock after receiving BOUGHT event
curl -X POST \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{"body": "cd key", "mimeType": "text/plain"}' \
https://gateway.kinguin.net/sales-manager-api/api/v1/offers/${OFFER_ID}/stock
6. Listen on delivered event
{
"name": "Testowe CD Key",
"price": {
"amount":2,
"currency":"EUR"
},
"priceIWTR": {
"amount":2,
"currency":"EUR"
},
"commissionRule": {"fixedAmount":0,"percentValue":0.0,"ruleName":"Zero"},
"productId": "${PRODUCT_ID}",
"offerId": "${OFFER_ID}",
"status": "DELIVERED",
"reservationId": "8a9de902-ac4b-4531-b207-cf9e74aaa2b8",
"releasedStockId": "${STOCK_ID}",
"releasedExternalStockId": "${EXTERNAL_STOCK_ID}",
"availableStock": 0,
"buyableStock": 1,
"declaredStock": 1,
"reservedStock": 0,
"updatedAt": "2020-03-06T16:00:12.088+0000",
"popularityBid":{
"amount": 0,
"currency": "EUR"
}
}
4.1 Declared text stock
Some of our clients and sales channels require keys to be delivered in text form. In order to properly handle such cases, we need not only to provide information on the amount of available text stock, but also to provide this type of key, if the customer request this type of key, when placing the order. In order to properly handle this process, when defining the amount of available stock, additionally set the value in the declaredTextStock field. If the customer orders a text stock (this can be recognized by the requestedKeyType=TEXT field in the webhook events), you should provide a text stock, additionally providing the reservation reference via the reservationId field.
Create an offer or with declaredStockand declaredTextStock. declaredTextStock should be lower or equal (when all the available stock is text) than declaredStock
curl -i -X PATCH \
-H "Authorization: Bearer ${BEARER}" \
-H "Content-Type: application/json" \
-d '{"declaredStock": 1, "declaredTextStock": 1}' \
https://gateway.kinguin.net/sales-manager-api/api/v1/offers/${OFFER_ID}
2. Upload stock with reservationId. You can use this field always when you want to fully control how keys are distributed across all orders.
curl -X POST \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{"body": "cd key", "mimeType": "text/plain", "reservationId": ${RESERVATION_ID}}' \
https://gateway.kinguin.net/sales-manager-api/api/v1/offers/${OFFER_ID}/stock
5. Introducing Smart Pricing Assistant (SPA)
The Smart Pricing Assistant (SPA) is a feature designed to empower merchants by automating the process of adjusting offer prices. With SPA, merchants can optimize their pricing strategies effortlessly, ensuring competitiveness and profitability. This chapter provides an in-depth look into the main features and processes of the SPA tool, along with examples of how to use it effectively.
5.1 Key Features
5.1.1 Automated Price Adjustments
SPA allows merchants to automate the adjustment of their offer prices based on predefined criteria. By utilizing the provided APIs, merchants can set specific conditions under which prices should be modified. SPA then continuously monitors these conditions and applies the necessary price changes, freeing merchants from the manual price adjustment process.
5.1.2 Precise Control
Merchants have full control over the price adjustment parameters. These parameters include:
the key cost
minimum profit
which sum up to your I Want To Receive (IWTR) value. Merchants can fine-tune these parameters to align with their business goals and strategies, ensuring that the automated price adjustments reflect their pricing preferences.
5.1.3 Real-time Updates
SPA operates in real-time, ensuring that price adjustments are promptly applied whenever the predefined conditions are met. This guarantees that merchants remain competitive and responsive to market dynamics without delays.
5.2 Pricing Logic
The Smart Pricing Assistant (SPA) operates on a sophisticated logic designed to optimize the position of a merchant's offer on tKinguin. SPA's primary objective is to enhance the competitiveness and profitability of the merchant by automating price adjustments based on market conditions. The key pricing principles of SPA are outlined below.
5.2.1 Competitive Positioning
SPA aims to position the merchant's offer as competitively as possible. It achieves this by pricing the offer just 0,01 Euro below the competing offer.
a. Outbidding Competitive Price
If a competitive offer is found, SPA will automatically reduce the price of the offer to be 0.01 Euro lower than the competitive price, as long as this reduction still falls within the minimum price set by the user.
b. No Outbidding Competitive Price
If the minimum price set by the user is higher than the found competitive offer but lower than the current user price, SPA will increase the user's price by 0,01 Euro to make it 1 cent lower than the offer that is one position below. This adjustment is made to maintain the current offer position while obtaining a higher price for it.
5.2.2 Best Possible Position
SPA always strives to secure the best available position for the merchant's offer. However, this positioning is subject to certain constraints: the key cost and the minimum profit amount defined by the merchant.
5.2.3 Flexibility
If the merchant's offer cannot attain the first position due to higher key costs or minimum profit requirements, SPA adjusts the offer's price to be 0.01 Euro below the next available position. This flexibility ensures that the offer remains competitive while adhering to the merchant's pricing strategy.
5.2.4 Maximum Price
If the found offer is higher than the user's maximum price after considering the minimum reduction, SPA sets the product/service price to the maximum price.
The Maximum Profit is an optional field (maximum price will be calculated without it).
5.3 Processes
5.3.1 Activating SPA for Offers
To activate the Smart Pricing Assistant for a specific offer, the following API call can be used:
curl -X POST \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '[{"offerId":"63e21a36814762653336ba8f","keyCost":300,"minProfitAmount":40,"minPriceIWTR":{"amount":340,"currency":"EUR"}, "maxProfitAmount":90}]' \
https://gateway.kinguin.net/pizzaportal/api/v1/spa/offers
Request allows to activate SPA in more than 1 offer at the time (array of offers in a body request). Once activated, SPA will automatically adjust the offer's price based on the defined criteria.
Detailed description of the body request
Field name | Description |
offerId | ID of the offer in which the SPA is to be activated |
keyCost | cost per key (euro cents) |
minProfitAmount | the minimum profit per key (euro cents) |
minPriceIWTR * {amount, currency} | the minimum amount that can be obtained for a key in euro cents (IWTR - I Want To Receive - the net price that the seller will receive) *optional field - if not provided it will be calculated from the formula keyCost + minProfitAmount |
maxProfitAmount * | the maximum profit per key (euro cents) *optional field - no maximum price limit, field is required if maxPriceIWTR provided |
maxPriceIWTR * {amount, currency} | the maximum amount that can be obtained for a key in euro cents (IWTR - I Want To Receive - the net price that the seller will receive) *optional field - if not provided it will be calculated from the formula keyCost + maxProfitAmount |
5.3.2 Updating Minimum Price
Merchants can update the minimum price, key cost, and minimum profit amount for an offer with the following API call:
curl -X PUT \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{"minPriceIWTR":{"amount":344,"currency":"EUR"},"keyCost":300,"minProfitAmount":44}' \
https://gateway.kinguin.net/pizzaportal/api/v1/spa/offers/${OFFER_ID}
This API call allows merchants to modify the price adjustment parameters, thereby influencing SPA's automated pricing decisions.
Detailed description of the body request
Field name | Description |
keyCost | cost per key (euro cents) |
minProfitAmount | the minimum profit per key (euro cents) |
minPriceIWTR * {amount, currency} | the minimum amount that can be obtained for a key in euro cents (IWTR - I Want To Receive - the net price that the seller will receive) *optional field - if not provided it will be calculated from the formula keyCost + minProfitAmount |
maxProfitAmount * | the maximum profit per key (euro cents) *optional field - no maximum price limit, field is required if maxPriceIWTR provided |
maxPriceIWTR * {amount, currency} | the maximum amount that can be obtained for a key in euro cents (IWTR - I Want To Receive - the net price that the seller will receive) *optional field - if not provided it will be calculated from the formula keyCost + maxProfitAmount |
5.3.3 Deactivating SPA for an Offer
To deactivate SPA for a specific offer, merchants can use the following API call:
curl -X DELETE \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
https://gateway.kinguin.net/pizzaportal/api/v1/spa/offers/deactivate/${OFFER_ID}"
This API call removes the automated price adjustment feature for the selected offer, allowing merchants to resume manual control over pricing.
6. Introducing Sales Booster
Sales Booster is an innovative tool tailored for merchants who seek enhanced visibility on the Kinguin’s product page buy button despite price-related limitations. This chapter delves into the key features and processes of the Sales Booster, demonstrating how merchants can strategically leverage this tool to increase their presence and boost sales on the platform.
6.1.1 Visibility Enhancement
Sales Booster is designed to give merchants an opportunity to stand out on Kinguin, even when they can't compete purely on price. By utilizing the provided endpoints, merchants can strategically bid for better positioning on the buy button, allowing them to catch the attention of potential customers.
6.1.2 Strategic Bidding
Merchants have the flexibility to place bids for improved positioning based on their marketing strategy and budget. This enables them to allocate resources effectively to enhance their offer’s visibility.
6.2 Processes
6.2.1 Requesting Visibility Boost
To enhance the visibility of an offer using the Sales Booster, merchants can send a POST request to the following endpoint:
POST /sales-manager-api/api/v1/offers/{OFFER_ID}/popularity
This API call allows merchants to strategically place bids to improve their offer's visibility on the buy button. The offer ID should be replaced with the corresponding offer's identifier.
6.2.2 Checking Position and Bid Impact
Merchants can assess the potential impact of their bid on their offer's position using the following GET request:
GET /sales-manager-api/api/v1/offers/{OFFER_ID}/position?bid={bid}&price={price}
This API call provides merchants with insights into how their offer's position would change based on the specified bid and price parameters. It's a valuable tool to make informed decisions regarding bid amounts.
7. FAQ
If someone order 2 keys from product X and 2 keys from product Y (in one order), then 4 BOUGHT events will be sent to the API?
Yes, events are sent for each key separately. This allows tracking changes for each key.
When I have 2 keys "up front" stock on Kinguin's offer and 20 as "declaredStock", someone bought 4 keys in one order. What parameters will be sent to the API (availableStock, buyableStock, declaredStock, reservedStock)?
When we assume there is no other order(s) on pending process:
{
[...]
"availableStock": 2,
"buyableStock": 22,
"declaredStock": 20,
"reserverdStock": 4,
[...]
}
When exactly OUT_OF_STOCK event will be sent? Only after 30 min after order, when my API does not sent enough keys before?
When there is no availableStock ("up front") then event OUT_OF_STOCK is sent right after BOUGHT.
When there is a reservation in status BOUGHT longer than 30 minutes you will get event OUT_OF_STOCK again.
You should be able to handle many OUT_OF_STOCK for the same reservation.
There are cases when OUT_OF_STOCK is not sent?
When you have availableStock (uploaded stock on "up front" approach) in the offer then OUT_OF_STOCK event is not sent
In the events JSON payload there is a price and currency. How to ensure that currency will be always EUR?
At the moment all orders are billed in EUR, and we don't plan to change this in known future
Can I assume that when my API gets OUT_OF_STOCK event, then it should add exactly one key to the offer, and everything will be fine?
Yes. We advise to listen on DELIVEREDevent to keep track of which reservation the key has been assigned to
Does API response codes to the events mean something? If there will be non-2xx then it will be tried once again?
Yes, when you return other status than 2xx, we will try to deliver event again (5 tries, and then after 10 minutes again 5 tries)
If we set declaredStock=0 and there will be no up-front stock (availableStock=0), then should be change the status of the offer, or it will be done automatically?
Offer disappears from Product Page, and customers cannot purchase from it. Status change is not needed. Uploading keys or changing declaredStock value will make the offer buyable again.
When my offer is visible for users and their can buy it
Offer is visible for buyers when buyableStock is greater than 0 (zero) and offer status is ACTIVE and offer is not blocked
How buyableStock is calculated
buyableStock = availableStock + declaredStock - reservedStock
How long reservation can be in BUYING status
It’s max 72h when reservation is canceled or bought
What happens if I don't deliver the declared stock?
If the stock is not delivered within 15 minutes, an alert will be generated that will affect the seller's rating. Then after about 10-15 minutes, the reservation will be canceled and the offer will be temporarily blocked (for about 4h)
Appendix
Events
Our system is able to broadcast notifications about Kinguin’s marketplace ordering process related to given merchant’s offers . These notifications aren’t automatically send to merchant. To enable those notifications you have to subscribe in our notification service (Envoy).
1. Events
Envoy is able to send webhooks for given Kinguin’s marketplace events:
Marketplace event | Webhook endpoint | Description |
BUYING | reserve | When user start buying process (go to payment gateway) |
BOUGHT | give | When we received payment from user |
CANCELED | cancel | When reservation is cancelled, e.g. payment gateway cancellation during payment. This event can appear only before key is delivered. |
DELIVERED | delivered | When key is delivered to buyer |
RETURNED | returned | When user did now saw key and during complaint process Kinguin agreed to “return key to stock”. Mostly user is refunded then. |
OUT_OF_STOCK | outofstock | When reservation was in BOUGHT state and key wasn’t uploaded to stock at time. This is “reminding” event that in some reservations are not delivered keys. Not providing stock at time can block merchant offer. |
REFUNDED | refunded | When reservation was refunded |
REVERSED | reversed | When payment for order was reversed |
PROCESSING_PREORDER | processingpreorder | When reservation is waiting for stock for the preorder, simillar to out_of_stock, but with diffrent restrictions regarding the time limit for stock delivery |
OFFER_POSITION_CHANGED | offerpositionchanged | When an offer has changed its position on the product’s offers list (e.g. when another merchant adds a better offer and his offer is above yours) |
You can find more information about particular events in Declared Stock approach integration section of such documentation.
2. Manage subscription
To receive whatever webhooks that Kinguin sent you have to setup your own subscription. This part will guide you how to configure (make / update) your subscription.
You are going to use endpoints which are protected and because of that we suggest you to read documentation Requirements and Authorization points. Next endpoints requires you to attach auth token in request’s headers. In case when You receive 401s as a HTTP code in responses this may
To create / update / view your subscription you will be using given endpoint:
Operation | Method | Payload | Headers | URL |
Make subscription | POST | See Making / updating subscription point. | Auth token required. | |
Update subscription | ||||
View subscription | GET | none |
Common valid HTTP code for each of those requests is 200.
Making / updating subscription
You can create subscription only once. You are able to modify your subscription multiple times depending on your needs.
Keep in mind that every change you made in subscription impacts only to newly created webhooks. Let say that you’ve change endpoint for DELIVERED event. Since that change every newly made webhooks are going to be delivered to updated endpoint. Old queued webhooks are going to be delivered to old endpoint.
Payload for given operations has two fields:
Field | Type | Required | Can be empty? |
endpoints | Map<String, URL> | true | yes |
headers | List<Map<String, String>> | true | yes |
Example of request payload:
{
"endpoints": {
"reserve": "https://your-service-domain.com/reserve_receiver",
"give": "https://your-service-domain.com/give_receiver",
"cancel": "https://your-service-domain.com/cancel_receiver",
"delivered": "https://your-service-domain.com/delivered_receiver",
"returned": "https://your-service-domain.com/returned_receiver",
"outofstock": "https://your-service-domain.com/outofstock_receiver",
"reversed": "https://your-service-domain.com/reversed_receiver",
"refunded": "https://your-service-domain.com/refunded_receiver",
"processingpreorder": "https://your-service-domain.com/processingpreorder_receiver",
"offerpositionchanged": "https://your-service-domain.com/offerpositionchanged_receiver"
},
"headers": [
{
"name": "your-secret-token",
"value": "$@CrEt"
}
]
}
Given payload will make new subscription or update existed one. Crucial part of that payload is endpoints field which contains a map of key-value pairs where key is an endpoint name and URL as a value. As you can see endpoints keys refers to webhook endpoints in Events paragraph. It means that each endpoint is mapped to Kinguin’s marketplace events.
Every request (create / update / view) has the same structure of response. Common success HTTP code for each of those requests is 200.
Envoy sends webhooks over internet and delivers payload to publicly available endpoints. Due to security reasons we sugggest you to define your own security header. Each of Envoy’s request will contain your header and thus gives you ability to verify identity Envoy as a true sender of webhook.
Example of common response
{
"id": "8e78b7aa9c66c4d5bbe983546",
"endpoints": {
"reserve": "https://your-service-domain.com/reserve_receiver",
"give": "https://your-service-domain.com/give_receiver",
"cancel": "https://your-service-domain.com/cancel_receiver",
"delivered": "https://your-service-domain.com/delivered_receiver",
"returned": "https://your-service-domain.com/returned_receiver",
"outofstock": "https://your-service-domain.com/outofstock_receiver",
"reversed": "https://your-service-domain.com/reversed_receiver",
"refunded": "https://your-service-domain.com/refunded_receiver",
"processingpreorder": "https://your-service-domain.com/processingpreorder_receiver",
"offerpositionchanged": "https://your-service-domain.com/offerpositionchanged_receiver"
},
"subscriberId": "12345678",
"headers": [
{
"name": "your-secret-token",
"value": "$@CrEt"
}
]
}
Blocking webhooks - automatic prevention of unsuccessful requests
The mechanism of blocking webhooks is designed to automatically prevent unsuccessful webhook requests.
The purpose of this feature is to identify and take action when, within a 15-minute timeframe, every response returns a status code other than 200 OK or timeout. In such cases, the affected URL is placed on a block list and Kinguin will cease sending any webhooks to this address until it is manually unblocked. Additionally, a notification email is sent to the merchant.
This proactive approach ensures that webhook integrations remain efficient and reliable, preventing scenarios where all responses to a series of requests are unsuccessful.
Unblocking webhooks
After verifying that the URL is functioning correctly, you can reactivate webhook delivery to this address by sending the endpoint name in the POST request (the name should match the Webhook Endpoint name from the table in point 1. Events):
curl -i -X POST \
https://gateway.kinguin.net/envoy/api/v1/subscription/unblock \
-H 'authorization: Bearer ${BEARER}' \
-H 'content-type: application/json' \
-d '{ "endpoint": "reserve" }'
3. Requests (webhooks) history
Envoy gives you opportunity to review notifications which have been sent through Envoy service. To get your own requests (webhooks) history use endpoint:
Method | Headers | Expected response HTTP status | URL |
GET | Auth token required. | 200 |
Every thing that is 200.
Remember to attach auth token in request headers.
Example of response
{
"_embedded": {
"requestHistoryList": [
// collection of webhooks (look on webhook model below)
]
},
"_links": {
"first": {
"href": "https://gateway.kinguin.net/envoy2/api/v1/requests?page=0&size=1"
},
"self": {
"href": "https://gateway.kinguin.net/envoy2/api/v1/requests?page=0&size=1"
},
"next": {
"href": "https://gateway.kinguin.net/envoy2/api/v1/requests?page=1&size=1"
},
"last": {
"href": "https://gateway.kinguin.net/envoy2/api/v1/requests?page=2537&size=1"
}
},
"page": {
"size": 1,
"totalElements": 2538,
"totalPages": 2538,
"number": 0
}
}
Webhook model
Webhooks are located inside collection named _embedded.requestHistoryList. Below’s list contains fields which are part of single webhook document.
Field | Type | Description |
id | String | Request history ID. |
webhookRequestId | String | ID of webhook request. |
deployAttempt | Integer | Webhook request sent count (webhookRequestId) |
sentDate | Date | Webhook request sent date. Date format YYYY-MM-DDTHH:mm:ss.ms GMT is the timezone. |
destinationUrl | String/URL | URL on which webhook has been sent. |
Request | ||
request.id | String | ID of webhook request. |
request.endpointKey | String | The name of the endpoint (e.g. “reserve“, “give“…) |
request.headers | List | Collection of Header objects which have been attached to request. |
request.subscriberId | Integer | Merchant’s ID. |
request.state | String | State of request (for internal purposes). |
request.statusesLog | List | Log ledger for given webhook (for internal purposes). |
request.deployAttempts | Integer | Webhook request sent count |
request.createDate | Date | Creation date (for internal purposes). |
request.toDeployDate | Date or null | Deploy date (for internal purposes). |
request.lastDeployDate | Date | Deployment date (for internal purposes). |
request.toSent.body | String | Request’s payload which has been sent out. Serialized to JSON-like string. |
request.toSent.bodyId | String | ID of sent entity (e.g. ReservationID). |
Response | ||
response.responseStatus | NInteger | HTTP status that has been returned in response. |
response.responseBody | String or null | HTTP body response mapped to string that has been returned in response. |
Example of single webhook request document
{
"id": "89d752ce-0cd7-4038-976a-eee3e04df39d",
"webhookRequestId": "3fd6c1b4-3db5-4c38-a460-9e4f490c342e",
"deployAttempt": 1,
"request": {
"id": "3fd6c1b4-3db5-4c38-a460-9e4f490c342e",
"endpointKey": "delivered",
"headers": [
{
"name": "x-secret-key",
"value": "s3cr3t"
}
],
"subscriberId": "5005408",
"state": "DEPLOYED",
"statusesLog": [
{
"date": "2021-08-31T13:00:38.914+0000",
"state": "READY"
},
{
"date": "2021-08-31T13:00:39.163+0000",
"state": "DEPLOYED"
}
],
"deployAttempts": 1,
"createDate": "2021-08-31T13:00:38.914+0000",
"toDeployDate": null,
"lastDeployDate": "2021-08-31T13:00:39.163+0000",
"toSent": {
"body": "{\"reservationId\":\"039a219d-7a62-4978-b070-03d7a88bb3ee\",\"productId\":\"5c9b608d2539a4e8f1737129\",\"name\":\"Worms Armageddon Steam CD Key\",\"price\":{\"amount\":1120,\"currency\":\"EUR\"},\"priceIWTR\":{\"amount\":1000,\"currency\":\"EUR\"},\"offerId\":\"5f8842ba34825e0001c95465\",\"status\":\"DELIVERED\",\"updatedAt\":\"2021-08-31T13:00:38.884+0000\",\"orderIncrementId\":\"JBOZP02ECRT\",\"declaredStock\":0,\"reservedStock\":26,\"availableStock\":253,\"buyableStock\":227,\"commissionRule\":{\"percentValue\":10,\"ruleName\":\"Test commision\",\"fixedAmount\":20},\"releasedStockId\":\"5e264018-3f66-4c35-95dc-ac72418720e2\",\"releasedExternalStockId\":null,\"popularityBid\":{\"amount\":4000,\"currency\":\"EUR\"}}",
"bodyId": "039a219d-7a62-4978-b070-03d7a88bb3ee"
}
},
"response": {
"responseStatus": 200,
"responseBody": null
},
"sentDate": "2021-08-31T13:00:39.163+0000",
"destinationUrl": "https://webhook.site/5c46edb4-e3d5-4737-b580-43dd1b1eb32c"
}
4. Webhook request sending and Re-tries
Every webhook request is send until don’t get response with status 200, but not often than 5 times. After every failed attempt envoy delays next send. First send is immediately. Second after 30 seconds, third after 60 seconds, fourth after 5 minutes and fifth after 15 minutes on last failed delivery.
There is possibility to re-try on demand making request:
curl -X POST \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{"webhookRequestId": "[webhookRequestId]"}' \
https://gateway.kinguin.net/envoy2/api/v1/requests/retry
Updating Offer
changing price
curl -i -X PATCH \
-H "Authorization: Bearer ${BEARER}" \
-H "Content-Type: application/json" \
-d '{"price": {"amount": 2, "currency": "EUR"}}' \
https://gateway.kinguin.net/sales-manager-api/api/v1/offers/${OFFER_ID}
response:
{
"id":"${OFFER_ID}",
"productId":"${PRODUCT_ID}",
"name":"Testowe CD Key",
"sellerId":5005408,
"status":"ACTIVE",
"block":null,
"priceIWTR":{
"amount":2,
"currency":"EUR"
},
"declaredStock":0,
"reservedStock":0,
"availableStock":0,
"updatedAt":"2020-03-06T10:58:49.088+0000",
"createdAt":"2020-03-06T10:58:48.951+0000",
"buyableStock":0
}
Updating wholesale tiers in Offer
Offer can be set to be sold wholesale. To enable offer as available for wholesale it has to have enabled set to true, and sell tiers need to be defined. There are four (4) tiers that decrease prices depending on bought keys amount.
level 1 above 0 to 10 pcs
level 2 above 10 to 20 pcs
level 3 above 20 to 50 pcs
level 4 above 50 pcs
discount attribute in the tier is a percent discount off the offer's “I Want To Receive” Price
Examples of API usage below.
Setting up wholesale without discounts
curl -i -X PATCH \
-H "Authorization: Bearer ${BEARER}" \
-H "Content-Type: application/json" \
-d '{"wholesale":{"enabled":true,"name":"custom", "tiers": [{"level": 1, "discount": 0},{"level": 2, "discount": 0},{"level": 3, "discount": 0},{"level": 4, "discount": 0}]}}' \
https://gateway.kinguin.net/sales-manager-api/api/v1/offers/${OFFER_ID}
response:
{
"id": "5f8842ba34825e0001c95465",
[...]
"priceIWTR": {
"amount": 1000,
"currency": "EUR"
},
"price": {
"amount": 1320,
"currency": "EUR"
},
[...]
"wholesale": {
"name": "custom",
"enabled": true,
"tiers": [
{
"discount": 0,
"level": 1,
"price": {
"amount": 1060,
"currency": "EUR"
},
"priceIWTR": {
"amount": 1000,
"currency": "EUR"
}
},
{
"discount": 0,
"level": 2,
"price": {
"amount": 1060,
"currency": "EUR"
},
"priceIWTR": {
"amount": 1000,
"currency": "EUR"
}
},
{
"discount": 0,
"level": 3,
"price": {
"amount": 1060,
"currency": "EUR"
},
"priceIWTR": {
"amount": 1000,
"currency": "EUR"
}
},
{
"discount": 0,
"level": 4,
"price": {
"amount": 1060,
"currency": "EUR"
},
"priceIWTR": {
"amount": 1000,
"currency": "EUR"
}
}
]
},
[...]
}
Setting up wholesale with discounts, 0% for sales smaller or equal than 10 pcs, 4% for sales between 11-20 pcs, 5% for sales between 21-50 pcs, 6% for sales above 6%
curl -i -X PATCH \
-H "Authorization: Bearer ${BEARER}" \
-H "Content-Type: application/json" \
-d '{"price": { "amount": 1000, "currency": "EUR"}, "wholesale":{"enabled":true,"name":"custom", "tiers": [{"level": 1, "discount": 0},{"level": 2, "discount": 4},{"level": 3, "discount": 5},{"level": 4, "discount": 6}]}}' \
https://gateway.kinguin.net/sales-manager-api/api/v1/offers/${OFFER_ID}
response:
{
"id": "5f8842ba34825e0001c95465",
[...]
"priceIWTR": {
"amount": 1000,
"currency": "EUR"
},
"price": {
"amount": 1320,
"currency": "EUR"
},
[...]
"wholesale": {
"name": "custom",
"enabled": true,
"tiers": [
{
"discount": 0,
"level": 1,
"price": {
"amount": 1060,
"currency": "EUR"
},
"priceIWTR": {
"amount": 1000,
"currency": "EUR"
}
},
{
"discount": 4,
"level": 2,
"price": {
"amount": 1018,
"currency": "EUR"
},
"priceIWTR": {
"amount": 960,
"currency": "EUR"
}
},
{
"discount": 5,
"level": 3,
"price": {
"amount": 1007,
"currency": "EUR"
},
"priceIWTR": {
"amount": 950,
"currency": "EUR"
}
},
{
"discount": 6,
"level": 4,
"price": {
"amount": 996,
"currency": "EUR"
},
"priceIWTR": {
"amount": 940,
"currency": "EUR"
}
}
]
},
[...]
}
Disable offer from wholesale
curl -i -X PATCH \
-H "Authorization: Bearer ${BEARER}" \
-H "Content-Type: application/json" \
-d '{"wholesale":{"enabled":false,"name":"dontSell"}}' \
https://gateway.kinguin.net/sales-manager-api/api/v1/offers/${OFFER_ID}
Matching products
Platforms:
[
{
"id":2,
"name":"Steam"
},
{
"id":1,
"name":"EA Origin"
},
{
"id":10,
"name":"XBOX ONE"
},
{
"id":7,
"name":"XBOX 360"
},
{
"id":5,
"name":"Uplay"
},
{
"id":3,
"name":"Battle.net"
},
{
"id":15,
"name":"GOG COM"
},
{
"id":18,
"name":"Epic Games"
},
{
"id":8,
"name":"PlayStation 3"
},
{
"id":11,
"name":"PlayStation 4"
},
{
"id":14,
"name":"PlayStation Vita"
},
{
"id":6,
"name":"Kinguin"
},
{
"id":4,
"name":"NCSoft"
},
{
"id":17,
"name":"Nintendo"
},
{
"id":12,
"name":"Android"
},
{
"id":13,
"name":"Other"
}
]
Regions
[
{
"id":3,
"name":"Region free"
},
{
"id":1,
"name":"Europe"
},
{
"id":18,
"name":"North America"
},
{
"id":10,
"name":"RoW"
},
{
"id":4,
"name":"Other"
},
{
"id":12,
"name":"Asia"
},
{
"id":14,
"name":"Australia"
},
{
"id":15,
"name":"Brazil"
},
{
"id":9,
"name":"China"
},
{
"id":13,
"name":"Germany"
},
{
"id":16,
"name":"India"
},
{
"id":17,
"name":"Japan"
},
{
"id":11,
"name":"Latin America"
},
{
"id":5,
"name":"Outside Europe"
},
{
"id":6,
"name":"RU VPN"
},
{
"id":7,
"name":"Russia"
},
{
"id":8,
"name":"United Kingdom"
},
{
"id":2,
"name":"United States"
}
]
languages:
[
{
"id":"ar_AE",
"name":"Arabic"
},
{
"id":"bg_BG",
"name":"Bulgarian"
},
{
"id":"zh_CN",
"name":"Chinese"
},
{
"id":"cs_CZ",
"name":"Czech"
},
{
"id":"da_DK",
"name":"Danish"
},
{
"id":"nl_NL",
"name":"Dutch"
},
{
"id":"en_US",
"name":"English"
},
{
"id":"fi_FI",
"name":"Finnish"
},
{
"id":"fr_FR",
"name":"French"
},
{
"id":"de_DE",
"name":"German"
},
{
"id":"el_GR",
"name":"Greek"
},
{
"id":"hu_HU",
"name":"Hungarian"
},
{
"id":"it_IT",
"name":"Italian"
},
{
"id":"ja_JP",
"name":"Japanese"
},
{
"id":"ko_KR",
"name":"Korean"
},
{
"id":"nn_NO",
"name":"Norwegian"
},
{
"id":"pl_PL",
"name":"Polish"
},
{
"id":"pt_PT",
"name":"Portuguese"
},
{
"id":"ro_RO",
"name":"Romanian"
},
{
"id":"ru_RU",
"name":"Russian"
},
{
"id":"es_ES",
"name":"Spanish"
},
{
"id":"sv_SE",
"name":"Swedish"
},
{
"id":"th_TH",
"name":"Thai"
},
{
"id":"tr_TR",
"name":"Turkish"
},
{
"id":"uk_UA",
"name":"Ukrainian"
}
]";"
To use more than one use comma(s)
Finding the valid productId by name
TODO
Finding the valid productId by kinguin catalog_id (old id)
To get productId you can use Kinguin Product Catalog endpoint
request
curl 'https://gateway.kinguin.net/kpc/api/v1/products/search/findByTypeAndExternalId?type=kinguin&externalId=56966' | jq "._embedded.products[0].id"
response
"5c9b786b2539a4e8f1842c1c"
[Value in Cents] Getting I Want to Receive Price from Offer Price (with Kinguin commision)
request
curl --request GET 'https://gateway.kinguin.net/sales-manager-api/api/v1/offers/calculations/priceAndCommission?kpcProductId=$[kpcProductId]&price=[$price] \
--header 'Authorization: Bearer ${BEARER}
response:
{
"rule": "Rule name",
"priceIWTR": 100,
"price": 125,
"fixedAmount": 0,
"percentValue": 25
}
[Value in Cents] Getting Offer Price (with Kinguin commision) from I Want to Receive Price
request
curl --request GET 'https://gateway.kinguin.net/sales-manager-api/api/v1/offers/calculations/priceAndCommission?kpcProductId=$[kpcProductId]&priceIWTR=[$priceIWTR] \
--header 'Authorization: Bearer ${BEARER}
response:
{
"rule": "Rule name",
"priceIWTR": 100,
"price": 125,
"fixedAmount": 0,
"percentValue": 25
}