Backend Integration

Custom asset server integration

It is possible to load items/currencies from a custom asset server and use them as tournament rewards or entry fees. Also, any sign up can be routed through a custom server that can deduct those items or reject tournament signup (e.g. user does not have enough currency).

Login with custom server

In order to authenticate login via custom server an HTTP endpoint has to be created that returns user basic info in a specific, JSON format. You have to do this if you want to have your user IDs associated with tournament account. Associated user IDs will then be provided in other custom server integration calls (e.g. reporting tournament results to custom server).

Data passed from the client for user authetication will be attached as application/x-www-form-urlencoded. Send data from client that are needed for user authentication (e.g. clinet token).

// coroutine
// ...
// use custom external login provider, pass custom data from your backend to authenticate user
var loginOperation = BackboneManager.Client.Login(LoginProvider.CustomExternal(
	true,
	MyBackend.Username,
	new KeyValuePair<string, object>("clientToken", MyBackend.Token),
	new KeyValuePair<string, object>("userId", MyBackend.Id)));
yield return loginOperation;

Check passed data from client and determine if user is valid. You have to respond with JSON format that contains your user ID and user nick.

 { 
 	"id": "123", 
 	"nickName": "nick" 
 }

The endpoint also has to respond with status code 200.

External store image

Once there is a endpoint that returns the required format, navigate to Game Settings/Authentication providers and enable custom authentication. Insert a valid HTTP path and add any necessary headers/parameters for authentication required by your server.

Loading store items from server

In order to load items and currencies from the custom server an HTTP endpoint has to be created that returns items in a specific, JSON format. You have to do this to make sure that the server loads the items for internal use and marks those items/currencies as "loaded" from the external resource.

Loading store items from server

JSON
{
   items:[
       {
           id:"itemId1", 
           name:"itemName1",
           image:"imageUrl (optional)"
       },
       {
           id:"itemId2", 
           name:"itemName2",
           image:"imageUrl (optional)"
       }],   
   currencies:[
       {
           id:"currencyId1", 
           name:"currencyName1"
       },
       {
           id:"currencyId2", 
           name:"currencyName2"
       }]
}

The endpoint also has to respond with status code 200. Always return all items you wish to import. Items/currencies that already exist will be updated.

External store image

Once there is a endpoint that returns the required format, navigate to Game Settings/Store Integration and enable "external store hookup". Select Custom URL provider. Insert a valid HTTP path and add any necessary headers/parameters for authentication required by your server.

Click on the Sync data now button which will import your items/currencies. You should now be able to access imported items when you are editing your tournament template rewards and entry fees.

If there are any updates to your assets just import them again by clicking on the Sync data now button.

Signup via custom server

In order to route tournament signups through a custom server, an HTTP endpoint has to be created that accepts a specific, JSON format. On every user signup, a call will be made to a custom server with a payload containing information about the tournament, user, entry fees, etc.

The HTTP call is POST with a parameter called userTicket containing the JSON payload.

Signup via custom server

JSON
{
   isSignup:true,
   isSignout:false,
   userId:"64bit number as string",
   userExternalId:"string",
   ticketId:"64bit number as string",
   tournamentId:"64bit number as string",
   customRequirements:[{
	name:"string",
	value:"string"
   }],
   fees:{
	items:[{
	   id:"64bit number as string",
	   externalId:"string",
	   amount:1
	}],
	currencies:[{
	   id:"64bit number as string",
	   externalId:"string",
	   amount:1
	}]
   }
}

A successful response is then awaited with HTTP status code 200 (or another code in case of rejection).

External signup image

Once there is a endpoint that accepts the required JSON format navigate to: Game Settings/Tournament settings Integration and enable "external signup". Insert a valid HTTP path and add any necessary headers/parameters for authentication required by your server.

After this setup, any signup from a client should be routed through the custom server before it's accepted and confirmed.

Tournament result report to custom server

To receive tournament results via a custom server, an HTTP endpoint has to be created that accepts a specific, JSON format. After every tournament finishes, a call will be made to a custom server with a payload containing information about the tournament, users and their prizes, etc.

The HTTP call is POST with a parameter called jsonPayload containing JSON payload.

Tournament result report to custom server payload

JSON
{
   "tournamentId":"64bit number as string",   
   "prizes":[{
       "ticketId":"64bit number as string",
       "userId":"64bit number as string",
       "userExternalId":"string",
       "place":1,
       "items":[{
           "id":"64bit number as string",
           "amount":1,
           "externalId":"string"}],
       "currencies":[{
           "id":"64bit number as string",
           "amount":1,
           "externalId":"string"}]
	}]
}

A successful response is then awaited with HTTP status code 200. In case of failure, it will be repeated several times.

External prize delivery image

Once there is an endpoint that accepts the required JSON format, navigate to: Game Settings/Tournament settings Integration and enable "external prize delivery". Insert a valid HTTP path and add any necessary headers/parameters for authentication required by your server.

PlayFab asset server integration

It is possible to load items/currencies from a PlayFab backend and use them as tournament rewards or entry fees. Also, any sign up can be routed through a custom cloud script that can deduct those items or reject tournament signup (e.g. user does not have enough currency).

Create PlayFab secret key

For PlayFab integration you will need TitleId and SecretKey. Login to your playfab dashboard, select your desired game and open settings screen.

PlayFab game settings

You can find TitleId in API Features tab. You can create a new SecretKey in Secret Key tab. Do not set expiration date for secret key as that could cause outtage for your game in case you forget to set new one in tournament dashboard.

Login with PlayFab provider

If you integrating with PlayFab services we strongly recommend enabling only PlayFab login provider in tournament dashboard. Also NOTE that if you will not use PlayFab login provider other integration features such as tournament signup or delivering prizes via cloudscript fail to work.

Dashboard playfab login provider image

Enter here TitleId & SecretKey and save changes.

In your game client, after you initialize and login with playfab you will get a session ticket. This session ticket is then passed to playfab login provider to authenticate user and login to tournaments. This process will ensure that system has correct and valid playfab id associated with user account.

string playfabId;
string playfabSessionTicket;
bool isPlayfabLoginFinished;

// coroutine
// ...
// login playfab client
PlayFab.PlayFabClientAPI.LoginWithCustomID(
    new PlayFab.ClientModels.LoginWithCustomIDRequest() { 
        CreateAccount = true, 
        CustomId = "customPlayfabId" 
    },
    (result) =>
    {
        isPlayfabLoginFinished = true;
        // get playfab user id and session ticket (we will use it for authentication)
        playfabId = result.PlayFabId;
        playfabSessionTicket = result.SessionTicket;
    },
    (error) =>
    {
        isPlayfabLoginFinished = true;
    });
// wait until playfab login process finishes
yield return new WaitUntil(() => isPlayfabLoginFinished);
// use playfab login provider, pass playfab id and session ticket
yield return BackboneManager.Client.Login(
    Gimmebreak.Backbone.User.LoginProvider.Playfab(
        true, 
        "NickName-" + playfabId, 
        playfabSessionTicket, 
        playfabId));

To get associated playfab id with user account call BackboneManager.Client.User.GetLoginId(LoginProvider.Platform.Playfab). This can be used to compare if logged playfab client matches with logged tournament client.

Loading store items from Playfab

Navigate to Game Settings/Store Integration and enable "external store hookup". Select Playfab provider. Insert a valid title id & secret key and save chages.

External store playfab image

Click on the Sync data now button which will import your items/currencies (From primary catalogue). You should now be able to access imported items when you are editing your tournament template rewards and entry fees.

If there are any updates to your assets just import them again by clicking on the Sync data now button.

Signup via Playfab CloudScript

In order to route tournament signups through a Playfab CloudScript, an function has to be created that accepts a specific, JSON format. On every user signup, a call will be made to this cloudscript funtion with a payload containing information about the tournament, user, entry fees, etc.

The CloudScript function parameter args will contain the JSON payload.

Signup via Playfab CloudScript

JSON
{
   serverSecret:"string",
   isSignup:true,
   isSignout:false,
   userId:"64bit number as string",
   userPlayfablId:"string",
   ticketId:"64bit number as string",
   tournamentId:"64bit number as string",
   customRequirements:[{
	name:"string",
	value:"string"
   }],
   fees:{
	items:[{
	   id:"64bit number as string",
	   externalId:"string",
	   amount:1
	}],
	currencies:[{
	   id:"64bit number as string",
	   externalId:"string",
	   amount:1
	}]
   }
}

Here is an example CloudScript function that handles signup/signout:

SignUp/SignOut

javascript
// This is a example tournament signup/signout handler. If signup is requested 
// check if user can sign up and deduct any required fees. If signout is requested
// return any deducted fees back to users account.
handlers.tournamentSignup = function(args, context){
    if (args.serverSecret == "secret") {
        if (args.isSignup) {
            // Check any custom requirement for the tournament. This can be 
            // e.g. having required minimum rank. If player does not meet
            // specified criteria, the signup should be rejected.
            for (var i = 0; i < args.customRequirements.length; i++) {
                var name = args.customRequirements[i].name;
                var value = args.customRequirements[i].value;
                if (name == "testRequirement" &&
                    value == "reject") {
                    return { success: false };
                }
            }
            // Check if user has required signup fees.
            if (args.fees.items.length > 0 ||
                args.fees.currencies.length > 0) {
                // Get user inventory
                var userInvetoryResult = server.GetUserInventory({PlayFabId: currentPlayerId});
                // Check if user has enough currency
                for (var i = 0; i < args.fees.currencies.length; i++) {
                    var currencyFee = args.fees.currencies[i];
                    var userCurrency = userInvetoryResult.VirtualCurrency[currencyFee.externalId];
                    if (!userCurrency ||
                        userCurrency < currencyFee.amount) {
                        // User does not have required currency or amount
                        return { success: false };
                    }
                }
                // Sort user invetory items by id
                var userInventory = {};
                for (var i = 0; i < userInvetoryResult.Inventory.length; i++) {
                    var item = userInvetoryResult.Inventory[i];
                    if (!userInventory[item.ItemId]) {
                        userInventory[item.ItemId] = [];
                    }
                    userInventory[item.ItemId].push(item);
                }
                // Check if user has enough items
                for (var i = 0; i < args.fees.items.length; i++) {
                    var itemFee = args.fees.items[i];
                    var userItems = userInventory[itemFee.externalId];
                    if (!userItems ||
                        userItems.length < itemFee.amount) {
                        // User does not have required item or amount
                        return { success: false };
                    }
                }

                // Substract user's currencies
                for (var i = 0; i < args.fees.currencies.length; i++) {
                    var currencyFee = args.fees.currencies[i];
                    server.SubtractUserVirtualCurrency({PlayFabId: currentPlayerId, VirtualCurrency: currencyFee.externalId, Amount: currencyFee.amount });
                }

                // Revoke user's items
                var revokedItems = { Items: [] };
                for (var i = 0; i < args.fees.items.length; i++) {
                    var itemFee = args.fees.items[i];
                    for (var p = 0; p < itemFee.amount; p++) {
                        revokedItems.Items.push({PlayFabId: currentPlayerId, ItemInstanceId: userInventory[itemFee.externalId][p].ItemInstanceId});
                        // Maximum 25 items can be removed at once (Playfab documentation)
                        // If container is filled with 25 items, revoke and continue
                        if (revokedItems.Items.length == 25) {
                            server.RevokeInventoryItems(revokedItems);
                            revokedItems.Items = [];
                        }
                    }
                }
                // Check if any items should be revoked (last 25 items)
                if (revokedItems.Items.length > 0) {
                    server.RevokeInventoryItems(revokedItems);
                }

                // All fees deducted, confirm tournament signup
                return { success: true };
            }
            else {
                // No fees to deduct, confirm tournament signup
                return { success: true };
            }
        }
        if (args.isSignout) {
            // Return user's currencies
            for (var i = 0; i < args.fees.currencies.length; i++) {
                var currencyFee = args.fees.currencies[i];
                server.AddUserVirtualCurrency({PlayFabId: currentPlayerId, VirtualCurrency: currencyFee.externalId, Amount: currencyFee.amount });
            }
            // Return user's items
            var returnItems = { PlayFabId: currentPlayerId, ItemIds: [], Annotation: "Returned tournament fee items. TournamentId: " + args.tournamentId };
            for (var i = 0; i < args.fees.items.length; i++) {
                var itemFee = args.fees.items[i];
                for (var p = 0; p < itemFee.amount; p++) {
                    returnItems.ItemIds.push(itemFee.externalId);
                }
            }
            // Check if any items should be returned
            if (returnItems.ItemIds.length > 0) {
                server.GrantItemsToUser(returnItems);
            }
            // User fees has been returned
            return { success: true };
        }
    }
    return { success: false };
};

A successful response is then awaited in form success: true (or anything else in case of rejection).

External signup playfab image

Once there is a CloudScript function that accepts the required JSON format navigate to: Game Settings/Tournament settings Integration and enable "external signup". Select Playfab provider. Insert a valid title id, secret key, CloudScript function name, function version (revision number) and save changes.

There is pre-generated server secret that you can change if you want to. Use this secret in cloud script to prevent playfab user clients to execute it.

After this setup, any signup from a client should be routed through the cloud script before it's accepted and confirmed.

Tournament result via Playfab CloudScript

To receive tournament results via a Playfab CloudScript, an function has to be created that accepts a specific, JSON format. After every tournament finishes, a call will be made to this cloudscript funtion with a payload containing information about the tournament, users and their prizes, etc.

The CloudScript function parameter args will contain the JSON payload.

Tournament result via Playfab CloudScript

JSON
{
   "serverSecret":"string",
   "tournamentId":"64bit number as string",   
   "prizes":[{
       "ticketId":"64bit number as string",
       "userId":"64bit number as string",
       "userPlayfabId":"string",
       "place":1,
       "items":[{
           "id":"64bit number as string",
           "amount":1,
           "externalId":"string"}],
       "currencies":[{
           "id":"64bit number as string",
           "amount":1,
           "externalId":"string"}]
	}]
}

Here is an example CloudScript function that handles prize delivery:

// This is example tournament prize delivery handler. Once tournament is finished
// all results will be provided togather with information which items and currencies
// should be granted to user.
handlers.tournamentPrizeDelivery = function(args, context){
    if (args.serverSecret == "secret" &&
        args.prizes) {
        for (var i = 0; i < args.prizes.length; i++) {
            var prize  = args.prizes[i];
            // Grant user currency prizes
            for (var p = 0; p < prize.currencies.length; p++) {
                var wonCurrency = {
                    PlayFabId: prize.userPlayfabId, 
                    VirtualCurrency: prize.currencies[p].externalId, 
                    Amount: prize.currencies[p].amount 
                    
                };
                server.AddUserVirtualCurrency(wonCurrency);
            }
            // Grant user item prizes
            var wonItems = { 
                PlayFabId: prize.userPlayfabId, 
                ItemIds: [], 
                Annotation: "Won items in tournament. Place: " + prize.place + " TournamentId: " + args.tournamentId
            };
            for (var p = 0; p < prize.items.length; p++) {
                var itemPrize = prize.items[p];
                for (var c = 0; c < itemPrize.amount; c++) {
                    wonItems.ItemIds.push(itemPrize.externalId);
                }
            }
            if (wonItems.ItemIds.length > 0) {
                server.GrantItemsToUser(wonItems);
            }
        }
    }
};

A successful response is then awaited with HTTP status code 200. In case of failure, it will be repeated several times.

External prize delivery playfab image

Once there is an endpoint that accepts the required JSON format, navigate to: Game Settings/Tournament settings Integration and enable "external prize delivery". Select Playfab provider. Insert a valid title id, secret key, CloudScript function name, function version (revision number) and save changes.

There is pre-generated server secret that you can change if you want to. Use this secret in cloud script to prevent playfab user clients to execute it.

Game server integration

Submit game session result from custom server

In order to process results from external server first navigate to: Game Settings/Tournament settings Integration and enable "external result submission".

To submit game session result from dedicated server or custom backed a specific HTTP call has to be made. The HTTP call is POST and the format has to be application/x-www-form-urlencoded.

Endpoint URL:

https://backbone-client-api.azurewebsites.net/api/v1/gameSessionSetResultFromServer

Headers:

BACKBONE_APP_ID: YOUR-TSDK-GAME-CLIENT-ID
ACCESS_TOKEN: YOU-WILL-BE-GIVEN-SERVER-ACCESS-TOKEN
Content-Type: application/x-www-form-urlencoded

application/x-www-form-urlencoded parameters:

gameSessionData

gameSessionData value has to be JSON string (do not include comments):

{
   // this is 64 bit number as string, id is provided from create game session API call, 
  "gameSessionId":"491197014055328536",
   // type of game session is anything you want between 0-127, default is 0
  // e.g.    0 - 1v1    1 - 2v2   etc you can give certain type to your games
  "type":"0",
  // this is 64 bit number as string, id is provided from match object e.g. match.Id
   // NB: NOT match.MatchId that is something different
  "tournamentMatchId":"491196930559318805",
   // time in seconds your game session lasted
  "time":"100",
   // list of users and their respective placements
  "users":[
    {
      "teamId":"1",
      "userId":"491174601636715209",
      "place":"2"
    },    
    {
      "teamId":"2",
      "userId":"491174351375178434",
      "place":"1"
    }
  ],
   // game session stats e.g. what type of map was played (optional)
  "gameStats":[
    {
      "statId":"2",
      "textValue":"test"
    }
  ],
  // user game session stats, e.g. how much damage specific user did (optional)
  "userStats":[
    {
      "statId":"1",
      "userId":"491174351375178434",
      "floatValue":"134.234"
    },
     {
      "statId":"1",
      "userId":"491174601636715209",
      "floatValue":"12.234"
    }
  ]
}

Get match data from server

Query tournament match data from game server.

Endpoint URL:

https://backbone-client-api.azurewebsites.net/api/v1/tournamentGetMatchDataForServer

Headers:

BACKBONE_APP_ID: YOUR-TSDK-GAME-CLIENT-ID
Accept-Encoding: gzip
Content-Type: application/x-www-form-urlencoded

application/x-www-form-urlencoded parameters:

accessToken
tournamentId
matchId
  • accessToken value has to be SERVER ACCESS TOKEN
  • tournamentId value has to be id of tournament match belongs to
  • matchId value has to be id of specific tournament match

WARNING When match is still searching for oppenents not all users are present in the results. Once match starts then returned users can be considered as immutable and final.

Response:

{
	"id": "620604471341234",
	"secret": "lz59JQiTfdgsdfg",
	"deadline": "2022-09-09T13:06:33.797Z",
	"matchId": 1,
	"phaseId": 1,
	"groupId": 1,
	"roundId": 1,
	"playedGameCount": 1,
	"maxGameCount": 1,
	"status": 8,
	"users": [
		{
			"userId": "6156293913241234",
			"userExternalId": "XXXX",
			"userPlayfabId": "XXXX",
			"teamId": 1,
			"checkedIn": false,
			"userScore": 0,
			"teamScore": 0,
			"userPoints": 0,
			"teamPoints": 0,
			"matchPoints": 0,
			"matchWinner": false
		},
		{
			"userId": "6203549123412344",
			"userExternalId": "XXXX",
			"userPlayfabId": "XXXX",
			"teamId": 2,
			"checkedIn": true,
			"userScore": 0,
			"teamScore": 0,
			"userPoints": 0,
			"teamPoints": 0,
			"matchPoints": 0,
			"matchWinner": false
		}
	],
	"roundSettings": [
		{
			"partySize": 1,
			"maxTeamsPerMatch": 16,
			"minGameTime": 3,
			"maxRoundTime": 6
		}
	],
	"propertySettings": [
		{
			"name": "property1",
			"value": "value1"
		},
		{
			"name": "property2",
			"value": "value2"
		}
	],
	"gameSessions": [
		{
			"gameSessionId": "6206051532412344"
		}
	]
}