External Game Backend integration

Respond to match events with your additional game backend service

What is the purpose of External Game Backend?

If you want to add player progression, play-to-earn functionality or smart contract integration to your game, you have to somehow exchange information about matches running on Elympics with your own backend.
To make that straightforward, we have implemented a feature to enable additional callbacks to your API from our services.

How to turn it on?

All you have to do is define endpoint for external game backend in the web panel (don’t forget to click the “Save” button):

External game backend setup in Elympics web panel

or via Elympics CLI:

elympics games update -g <your game ID> -b <base URI of your backend>

Changes should be applied within 5 minutes. Use Elympics public keys provided here to verify the integrity and authenticity of request.

Endpoints to implement

/elympics/match/create

A request to /elympics/match/create is sent after a match is created in our database, but before bootstrapping real game on one of our game servers. It could be used to:

  • insert information about the match in your database e.g. for statistical / progression purposes,
  • verify player-provided data for match initiation,
  • add and/or replace players initial data using one stored in your database,
  • block some players, game versions, etc.
{
	"MatchId": "d060f00d-0000-0000-0000-1d0000001234",
	"GameId": "d060f00d-0000-0000-0000-1d0000006a3e",
	"GameVersion": "1.2.3",
	"UserDatas": [
		{
			"UserId": "d060f00d-0000-0000-0000-1d0000085e70",
			"IsBot": false,
			"BotDifficulty": 0,
			"GameEngineData": "eyJlcXVpcG1lbnQiOlsia2F0YW5hIl0sImRvY3MtZXhhbXBsZSI6dHJ1ZX0=",
			"MatchmakerData": [
				1.0
			]
		},
		{
			"UserId": "d060f00d-0000-0000-0000-1d0000000b07",
			"IsBot": true,
			"BotDifficulty": 6.62607,
			"GameEngineData": null,
			"MatchmakerData": null
		}
	]
}
{
	"$schema": "https://json-schema.org/draft/2019-09/schema",
	"$ref": "#/definitions/MatchCreateRequestModel",
	"definitions": {
		"MatchCreateRequestModel": {
			"type": "object",
			"additionalProperties": false,
			"properties": {
				"MatchId": {
					"type": "string",
					"format": "uuid"
				},
				"GameId": {
					"type": "string",
					"format": "uuid"
				},
				"GameVersion": {
					"type": "string"
				},
				"UserDatas": {
					"type": "array",
					"items": {
						"$ref": "#/definitions/UserData"
					}
				}
			},
			"required": [
				"MatchId",
				"GameId",
				"GameVersion",
				"UserDatas"
			],
			"title": "MatchCreateRequestModel"
		},
		"UserData": {
			"type": "object",
			"additionalProperties": false,
			"properties": {
				"UserId": {
					"type": "string",
					"format": "uuid"
				},
				"IsBot": {
					"type": "boolean"
				},
				"BotDifficulty": {
					"type": "number"
				},
				"GameEngineData": {
					"type": ["string", "null"],
					"media": {
						"binaryEncoding": "base64",
						"type": "application/octet-stream"
					}
				},
				"MatchmakerData": {
					"type": ["array", "null"],
					"items": {
						"type": "number"
					}
				}
			},
			"required": [
				"UserId",
				"IsBot",
				"BotDifficulty"
			],
			"title": "UserData"
		}
	}
}
curl --location --request POST '{YOUR_BACKEND_URL}/elympics/match/create' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJoYXNoIjoiNGRZTVVVcHMvUVpRRWpzZmIrU1BJUGNTYXBsUTRUc1JpZUZBUDViNFZYRT0iLCJoYXNoLWFsZyI6InNoYTI1NiIsIm5iZiI6MTY3MjY0NTA1NCwiZXhwIjoxOTg4MjY0NjE4LCJpYXQiOjE2NzI2NDUwNTR9.KDm3rbNFTL3pGFl1Lgrqj6Hh7URUj75nC5lLzAcZL65I_9TcXMDXMrvl8pclb-Ct_uh-ObKkq_OJ6iL3ZyToNFVzI3QJdfAzvCufz6VlOP1YS7WlW4ef2mfH9hQJ1dK2FkPYmVLWQAgvvNerosJGJcbwAUk764KWvZMONu2X1kaWa54-iiXt1CnUhat5td4FUzjDDnOD7DPH5d13EIap1qVTNCrwvHyG-hZ6u_uPwNcqTXtgeyYO4-_QAbWfnE-C1laVlocXSPJGCtdyKa3vQUnpJPa5RTwg0Q9rXvEoO43T2GiKI_HWWmGScipYgW1XZihVHMJhO13xiy3NaA8BOH3SFXLwyPdhK9RPQ31J42sIqrrnwnlWgMXDELTt-HvySt82GnK5ktnXzLJsD-SHo86jUrA9NxklLhe0ycFABIKHtonyUsagqitgnO6Di77sdm-SMtkl4ecJgFkOWO3SQjflEZdQPvrg-N1JD5veCXvpzx0zFDwbWkmxrJf_qlS8qLx4d1LmfEh7_1xXP6Cf40-s-Mx4z348GLmLKz5K1RwMBmb6VSivm2agWSRs-zWnZd0rS2_cyThxTX30xOms7p3TlRexH-7yHAEVe-1BnTP-u47yT-nwdPIFKDwGd4bxsDlnN8U4OmiNI9BhXpxf6K8Mt0DA8ueTrbp3q6niY38' \
--header 'Content-Type: application/json' \
--data-raw $'{\n\t"MatchId": "d060f00d-0000-0000-0000-1d0000001234",\n\t"GameId": "d060f00d-0000-0000-0000-1d0000006a3e",\n\t"GameVersion": "1.2.3",\n\t"UserDatas": [\n\t\t{\n\t\t\t"UserId": "d060f00d-0000-0000-0000-1d0000085e70",\n\t\t\t"IsBot": false,\n\t\t\t"BotDifficulty": 0,\n\t\t\t"GameEngineData": "eyJlcXVpcG1lbnQiOlsia2F0YW5hIl0sImRvY3MtZXhhbXBsZSI6dHJ1ZX0=",\n\t\t\t"MatchmakerData": [\n\t\t\t\t1.0\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t"UserId": "d060f00d-0000-0000-0000-1d0000000b07",\n\t\t\t"IsBot": true,\n\t\t\t"BotDifficulty": 6.62607,\n\t\t\t"GameEngineData": null,\n\t\t\t"MatchmakerData": null\n\t\t}\n\t]\n}'
{
	"Allow": true,
	"RejectionCause": null,
	"UserDatas": [
		{
			"UserId": "d060f00d-0000-0000-0000-1d0000085e70",
			"IsBot": false,
			"BotDifficulty": 0,
			"GameEngineData": "eyJlcXVpcG1lbnQiOlsia2F0YW5hIl19",
			"MatchmakerData": [
				1
			]
		},
		{
			"UserId": "d060f00d-0000-0000-0000-1d0000000b07",
			"IsBot": true,
			"BotDifficulty": 6.62607,
			"GameEngineData": "eyJlcXVpcG1lbnQiOlsibWluaWd1biJdfQ==",
			"MatchmakerData": null
		}
	]
}
{
	"$schema": "https://json-schema.org/draft/2019-09/schema",
	"$ref": "#/definitions/MatchCreateResponseModel",
	"definitions": {
		"MatchCreateResponseModel": {
			"type": "object",
			"additionalProperties": false,
			"properties": {
				"Allow": {
					"type": "boolean"
				},
				"RejectionCause": {
					"type": ["string", "null"]
				},
				"UserDatas": {
					"type": ["array", "null"],
					"items": {
						"$ref": "#/definitions/UserData"
					}
				}
			},
			"required": [
				"Allow"
			],
			"if": {
				"properties": {
					"Allow": { "const": true }
				}
			},
			"then": {
				"required": ["UserDatas"]
			},
			"title": "MatchCreateResponseModel"
		},
		"UserData": {
			"type": "object",
			"additionalProperties": false,
			"properties": {
				"UserId": {
					"type": "string",
					"format": "uuid"
				},
				"IsBot": {
					"type": "boolean"
				},
				"BotDifficulty": {
					"type": "number"
				},
				"GameEngineData": {
					"type": ["string", "null"],
					"media": {
						"binaryEncoding": "base64",
						"type": "application/octet-stream"
					}
				},
				"MatchmakerData": {
					"type": ["array", "null"],
					"items": {
						"type": "number"
					}
				}
			},
			"required": [
				"UserId",
				"IsBot",
				"BotDifficulty"
			],
			"title": "UserData"
		}
	}
}

/elympics/match/finish

A request to /elympics/match/finish is made after a match is finished. It contains the information about match results that can be e.g. inserted into your database.

{
	"MatchId": "d060f00d-0000-0000-0000-1d0000001234",
	"GameId": "d060f00d-0000-0000-0000-1d0000006a3e",
	"GameVersion": "1.2.3",
	"UserDatas": [
		{
			"UserId": "d060f00d-0000-0000-0000-1d0000085e70",
			"IsBot": false,
			"BotDifficulty": 0,
			"GameEngineData": "",
			"MatchmakerData": [0.0, 3.0]
		},
		{
			"UserId": "d060f00d-0000-0000-0000-1d0000000b07",
			"IsBot": true,
			"BotDifficulty": 6.62607,
			"GameEngineData": "eyJkb2NzLWV4YW1wbGUiOnRydWV9",
			"MatchmakerData": [1.0, -1.0]
		}
	]
}
{
	"$schema": "https://json-schema.org/draft/2019-09/schema",
	"$ref": "#/definitions/MatchFinishRequestModel",
	"definitions": {
		"MatchFinishRequestModel": {
			"type": "object",
			"additionalProperties": false,
			"properties": {
				"MatchId": {
					"type": "string",
					"format": "uuid"
				},
				"GameId": {
					"type": "string",
					"format": "uuid"
				},
				"GameVersion": {
					"type": "string"
				},
				"UserDatas": {
					"type": ["array", "null"],
					"items": {
						"$ref": "#/definitions/UserData"
					}
				}
			},
			"required": [
				"MatchId",
				"GameId",
				"GameVersion"
			],
			"title": "MatchFinishRequestModel"
		},
		"UserData": {
			"type": "object",
			"additionalProperties": false,
			"properties": {
				"UserId": {
					"type": "string",
					"format": "uuid"
				},
				"IsBot": {
					"type": "boolean"
				},
				"BotDifficulty": {
					"type": "number"
				},
				"GameEngineData": {
					"type": ["string", "null"],
					"media": {
						"binaryEncoding": "base64",
						"type": "application/octet-stream"
					}
				},
				"MatchmakerData": {
					"type": ["array", "null"],
					"items": {
						"type": "number"
					}
				}
			},
			"required": [
				"UserId",
				"IsBot",
				"BotDifficulty"
			],
			"title": "UserData"
		}
	}
}
curl --location --request POST '{YOUR_BACKEND_URL}/elympics/match/finish' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJoYXNoIjoiTk93ZWM3WTNkMk1UdEh0ZXAra3MxWkNFcEZLbmd5d1VITGVuNStERW5vRT0iLCJoYXNoLWFsZyI6InNoYTI1NiIsIm5iZiI6MTY3MjY0NTA1NCwiZXhwIjoxOTg4MjY0NjE4LCJpYXQiOjE2NzI2NDUwNTR9.Z9yA8gQnqxlAJlmz-sghm5KaKEbQ5-6Yjf1LLtWkYBUWWM10voJczEuoC6DzcppMkKLpYTTEaJo_PRQdxRTtCXI2DrREd5kniacfw6WojldbL3Lp_Y_ZIGW9DSkOpaP9FDq21JM0dS1BUipAV537pIx6pIbcALoqYvxgyMsMOpDVvppImU7hXQdTmdzwNbZism56U5uV-AcGvpGgCSwBV8R-tfTC8Mtt6rE0SizVdYv8ciZ6poBPiOBS6HAIOWPjwqdkCCz5aUo5yFAgXhRv-4LV0CfZ6HmxXgWIrtktN6QFNNFyFJG3v5tPvcEr8aJ0N0E7SRaLaMbqZ8J2h9U8NHjrOt1WgXH54a1IIInLZJuPfEr97nInw47j9eFzNDaQR-qU_T1d_hDavawC2JhD8JetG5I1r_aXFCPcD5bEDudJxA9Kd7G1xUo4TD1cRscNkGp21bloqGEIBF_EW0j5MpqmtbRLmqEpjfoSZeMwW26Hj1FAmpWrwRQaxc8fRz6WgF6pdc5NPPfQ90uxP0c6ST1go_DTqwFD_frfci193lcTYha9dRbpq6lpgh4EkLpVZyrWgwmDbWVH5ZCaBH7BbdClaL3eODMoJC_BxlRjOKQtAbVAddQAdCJzH--mprxiOKXKzqLOW2AJX3pq3JAKESDSqOJtx5CbVRBtSkXa9sk' \
--header 'Content-Type: application/json' \
--data-raw $'{\n\t"MatchId": "d060f00d-0000-0000-0000-1d0000001234",\n\t"GameId": "d060f00d-0000-0000-0000-1d0000006a3e",\n\t"GameVersion": "1.2.3",\n\t"UserDatas": [\n\t\t{\n\t\t\t"UserId": "d060f00d-0000-0000-0000-1d0000085e70",\n\t\t\t"IsBot": false,\n\t\t\t"BotDifficulty": 0,\n\t\t\t"GameEngineData": "",\n\t\t\t"MatchmakerData": [0.0, 3.0]\n\t\t},\n\t\t{\n\t\t\t"UserId": "d060f00d-0000-0000-0000-1d0000000b07",\n\t\t\t"IsBot": true,\n\t\t\t"BotDifficulty": 6.62607,\n\t\t\t"GameEngineData": "eyJkb2NzLWV4YW1wbGUiOnRydWV9",\n\t\t\t"MatchmakerData": [1.0, -1.0]\n\t\t}\n\t]\n}'
{}
{
	"$schema": "https://json-schema.org/draft/2019-09/schema",
	"$ref": "#/definitions/MatchFinishResponseModel",
	"definitions": {
		"MatchFinishResponseModel": {
			"type": "object",
			"additionalProperties": false,
			"title": "MatchFinishResponseModel"
		}
	}
}

Request verification

JWT tokens used by Elympics are to be verified using the following public key:

-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnJIPvBq84i6rdwD6jagc
jFj6IZ3yDPGQCMm3+0WeGj1H7wDlIwA6CkvEVpi99HugkuxO7hNUY+ojwhRaDaKq
xby4K0NqCT/C7iSyTXtRGK8qR4lkkuYZbEk1p+j6I3SEcfxyXT1qVvjz6gJwFTT5
+ZXZkYKPsfxodQes7MaJPmuWN7GVZMef+qe6QsC9KsAgDDsBrk+gVPJTeAVTWlcH
WUwN2Nz56A4g7/CXkGgKYUNChmrt6Ex8PCQmXgj48kHHKiSxsqibl8bm9wVsthGf
yUhuRXnfnCh4xQo0uiGELJieogWUVfAgB7Z2edDijtFPJidyUJ2uos3/tYTtuTIo
kUdw/HkxHC1ByzkgOEtOAAXhd6mAL6UVmQlOGkclmXjq7/dY4B72NUnrhBO/nUGo
alLNl9ZMeLiAVlDGaw/EvS3+UH/YCubKb13QFW9Bp17bGjs3GiHz93MYXsttW7KW
shCK8Os8oVSjOSxWx8IKYF2cpuyKdhsr17MbSNb59USJTGJMgoHqqMR8TBALIVF0
uLVJaEf0RknocWrpMrIq12swevWnX3YpJ5v8TtJilCxpgut/Gv7hfX7bGqA+WEAB
4dyEuEb/QbrIrZt9v3J+1qgS8QEWzd0yIFTtv0+xL9Y2fVvsv3gNGBUgO7mi+f/a
9Mh/klF9HTgMI9WpZHFqZdkCAwEAAQ==
-----END PUBLIC KEY-----

Besides the standard not-before (nbf), expiration (exp) and issued-at (iat) times, each token provides hash and hash-alg claims that can be used to ensure the integrity of request body.

hash stores a checksum calculated using hashing algorithm described by hash-alg. The checksum is based solely on the contents of HTTP message body.

Additional endpoints

Your backend can provide an arbitrary number of additional endpoints to be utilized by game code. Those functions are not handled by Elympics in any way – you have to send web requests manually from your code.

When performing such requests, you can use a JWT authentication token granted to each user by Elympics. The token is accessible through Authenticated event of ElympicsLobbyClient singleton. You can find more information in the article about Lobby & Matchmaker.

Example cURL:

curl --location --request POST 'https://{YOUR_BACKEND_URL}/arbitrary/endpoint' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiJ0ZXN0LXVzZXIiLCJuYmYiOjE2NjQ1MzkxMDcsImV4cCI6MTk3OTg5OTEwNywiaWF0IjoxNjY0NTM5MTA3fQ.bBJmzPrybXDZMrwP9u9oWKs8v-6dRYhT-mLwnNJY3i6V4FdbceCZIb_IJYTHTKtCixHRAjG1--fvBYKt5zJRqfKlD6ic4VQajN0TVCljREHxHtEdtKcZSEKl9syyUpVDOnork77m3wgAUAagd7BDjuN6fXXmX_U0g0r4S-QAZdwsrSEMBrHJjJXw3cQ222pl4pR6JmWpV5gxeB_VIwLbLU-mLSvJZWDQa49t4lx2o0DM0_HcoKuVMjW-_LwtQusCl140PFJI18ecP3CAYFyAUgwcSH7OBw_VbWpu6-MVXLgRqJaovVEIibHXok26C9_3LjULlx_VaeuYncR-eAJ_A9qsCUlI77xuA0Azm3GoguUUdnbXN3UPOeBKx4q92HWD9gI9fFHiBDaRYoW7OqyckytHiu1wTMCAnF_UbRrwMQJrM6bJSp5rM6CfZanYuyvglD9KX5dWPMjaWDfyctKKHQ8nRy1ZZ45aBgIjzw-TioaNgDHDx0BMGvdRRohNYCTQQOZ7vToSebAMuyD9UDRZXGfq_u6XWabayE0OnxTXhn7QfMu2jm5_5MbgbBvk4uqPC1TETzo8Te2aCZLLXPO2YlW683gja7GGaNG-9xT9kb294dLJMD6hvUa96DK0h_XISG5iE1sM3B7MG1BU4iy-JGkkffDwK7hbhzODV2pFhFE' \
--header 'Content-Type: application/json' \
--data-raw '{
	"ExampleField": "Example data"
}'

Request verification

JWT tokens granted to users are to be verified using the following public key:

-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5ARBAzdGJqvN2NdThNsW
6GFU6Y45LAfDlAu892veBIeNeHESjuV2Nr9z2pCUM0iX3JYoNPqvESl1bDiCoWUc
G79l7aDZlm0OegZqmBFbfmifxQWvnNNXaqDyviwDgXxVCvqcEZF+sgfNQKK18sGO
YLQdpPGB3bVDqMooiO83B3CayahO0CEcC+aJInDp74qiCvNdfMv2gsmjVgKAzbyQ
pKX5bI8fqipbRg/LYHlOEBORKA54gL/QEuJvPf7Eme1kyzFdkBaXlCrVa060owpe
ozOSDzZ+fNIuARkoi0ymVID1WlVlZUzZNNjJn52qxI1zTUKIdPQZiibj93kf/iti
ZcB0DgBPrFa8LiO9cx9Eilumuyo66j9LPI8LylYRsahkmhuQaj5Gv1puHoptpeIu
9r5Q3yAdh7BGj0gx77jfHQhMayQy20mwFxr/fjhMxbDqnkcXYrH4TWekPqTB1sRT
EcS/kCd3lbDDIBAfxlEXYJnl1GqyjoHQ8VWzZVEjEeUsUBKwmPvQ9iD6Td1uZUyX
YY6CB6mjJN2FDeRNprYeGJyBAQBN+IpENWRAYIuN01KCoblA9bZT+Ql46jx5HCB/
jlEEjW862hR2H7YAxxXmEpvZX5R5Z5EzJS6w1P5vBXEnXOw7rqrGNfPExv8Wuq/7
IIOC6oC90KQmnrMc235LFI8CAwEAAQ==
-----END PUBLIC KEY-----

These tokens contain additional nameid claim storing the ID of a player.