- Last Updated
Game Sanction
Understanding
When you need to block a specific user's game access due to cheating or the like, the operator can sanction that user in STOVE Partners.
Once a sanction is registered, the STOVE platform delivers a Kick event to the game server.
The game server can receive this event and immediately eject the sanctioned user from the game.
Behavior by Sanction Timing
Sanctions are applied differently depending on the user's connection state.
| State | Mobile | PC |
|---|---|---|
| Connected (logged-in) state | Can use normally (no sanction pop-up) A sanction alert appears on re-login after token expiry |
Can use normally (no sanction pop-up) |
| Pre-connection (not logged-in) state | A sanction alert appears at login | A sanction alert appears when the launcher starts the game |


In-game Kick handling is needed for connected users
ㅁ In the connected (logged-in) state, the game can't recognize the STOVE sanction status.
ㅁ When a sanction is registered, the game server must handle the Kick and the client must handle logout/exit itself.
Sanction Event Flow
| Step | Action |
|---|---|
| ① Event delivery | On sanction registration, STOVE delivers a Kick event to the URL registered for the game |
| ② Game handling | Notify of the sanction; handle the in-game Kick and STOVE Logout |
| ③ Re-login | On user re-login, the detailed sanction reason is shown in a pop-up |
No separate event when a sanction is lifted
ㅁ The game only needs to handle the Kick for the sanctioned user.
Game-server environment prerequisites before integration
ㅁ We recommend an internal-network setup for the game API server, and an ACL setting is needed for STOVE ↔ game-server calls.
ㅁ If you have an Inbound ACL, allow the STOVE Event Broker's per-environment NAT IPs. (ask the STOVE technical contact for the IPs)
Development
Integration Overview
- User-sanction (Kick event) handling flow
- PC execution flow — checking sanction status for re-logging-in users
- Mobile execution flow — checking sanction status for re-logging-in users
- Additional verification flow for bypassing users
Callback API Configuration Guide
An endpoint that the CP company (developer) implements and operates directly; the STOVE Event Broker calls this URL when a Kick event occurs.
API endpoint
| Item | Details |
|---|---|
| URL | Provided by the CP company (e.g., https://game.example.com/stove/kick) |
| Method | POST |
| Protocol | HTTPS (TLS 1.2 or higher required) |
| Content-Type | application/json |
| Resend policy | If no HTTP 200 response, the same event is resent up to 3 times |
Request — Body specification
| Name | Type | Required | Description |
|---|---|---|---|
| event_reason | String | Y | The reason the event occurred. For a game sanction, GAME_RESTRICT. |
| event_message | String | Y | The event message. |
| event_time | Long | Y | The event occurrence time (milliseconds). |
| target_users | Long Array | Y | The list of user identifiers targeted for the Kick. |
Sample — Request
curl --location --request POST '{endpoint}' \
--header 'Content-Type: application/json' \
--data-raw '{
"event_reason": "GAME_RESTRICT",
"event_message": "Abnormal in-game trading behavior",
"event_time": 1739145600000,
"target_users": [ 20000000001, 20000000002, 20000000003, 20000000004, 20000000005 ]
}'
Sample — Response (Success)
HTTP/1.1 200 OK
{
"code": 0,
"message": "SUCCESS"
}
Sample — Response (Failure)
If the response code isn't 200, the STOVE Event Broker resends the same event up to 3 times.
HTTP/1.1 500 Internal Server Error
{
"code": 500,
"message": "FAILURE"
}
User Sanction-Info Lookup API
A lookup API called on the game server after verifying the user's token validity, to block bypass entry.
You can check the user's sanction status with guid and game_id, and if sanctioned, the sanction code and sanction-period info are returned. (the sanction reason isn't returned)
Identifier terminology (TBD — to be moved to a separate guide page once finalized)
| memberNo | The unique identifier of a STOVE account. Only one is assigned per user. A game-independent global identifier. |
| guid | A user identifier the platform issues per game. Even the same memberNo has a different guid per game.Conceptually a mapping of memberNo + game_id → guid. |
| game_id | The key that identifies the game (determined at Partners registration). Used as a path parameter of this API. |
※ This definition is currently being organized, and once the official identifier guide page is ready, this box will be replaced with a link to that page.
API endpoint
| Item | Details |
|---|---|
| URL | GET /mmember/v1.0/signin/game/status/{guid}/{game_id} |
| Host (Live) | https://api.onstove.com |
| Host (Sandbox) | https://api.gate8.com |
Request — Header
| Name | Type | Required | Description |
|---|---|---|---|
| Content-Type | application/json | Y | Resource media type. |
| Authorization | String | Y | The platform authentication token. In the form Bearer {API AccessToken}. |
| caller-id | String | Y | A header identifying the API caller. There's no fixed rule; the game server may define it freely and share it with STOVE in advance. Used as an identifier for monitoring metrics (call volume, failures, tracing). Example (recommended): {service_id}_SERVER, {service_id}_HOME※ service_id is the Game ID registered in Partners. We recommend not changing the value once set. |
Request — Path Params
| Name | Type | Required | Description |
|---|---|---|---|
| guid | String | Y | Game User ID. |
| game_id | String | Y | The game identifier (the Game ID registered in Partners). |
Request — Query Params
| Name | Type | Required | Description |
|---|---|---|---|
| guid_yn | String | Y (guid game) | For a guid game, Y is required. |
Response — Body
| Name | Type | Required | Description |
|---|---|---|---|
| response_code | int | Y | The response code (success: 0). |
| response_message | String | Y | The response message (success: Success). |
| value.RESTRICT | Object | N | Sanction info. Included only when sanctioned. |
Response — Sanction info (value.RESTRICT)
| Name | Type | Example | Description |
|---|---|---|---|
| memberNo | Long | 1234 | The user's unique number (guid). |
| nickname | String | AUTONICK##9515338 | The character nickname. |
| banDay | int | 2 | The remaining sanction period (days). |
| banCd | String | B026 | The sanction code (defined in each game's master data). |
| banStartDt | Long | 1649053320000 | The sanction start date (milliseconds). |
| banEndDt | Long | 1649312520000 | The sanction end date (milliseconds). |
Response — Response code (response_code)
All responses come back as HTTP 200, and you should branch on the response_code value to handle the business result.
| Code | Constant name | Meaning / handling guide |
|---|---|---|
| 0 | PALMPLE_OK | Normal response. If value.RESTRICT is present, sanctioned; if absent, no sanction. |
| 10003 | PALMPLE_ERR_NO_DATA | No sanction info. Treat as a normal user. |
| 10122 | PALMPLE_ERR_BAN_MEMBER | Sanctioned user. In-game Kick and STOVE Logout handling required. |
| 90001 | AUTH_ACCESS_TOKEN_INVALID | Token verification failed. Check that the caller-id/Authorization headers or the path's guid/game_id match, then retry. |
Sample — Request
curl --location 'https://api.gate8.com/mmember/v1.0/signin/game/status/12345/{game_id}?guid_yn=Y' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer {API accessToken}' \
--header 'caller-id: {caller-id}'
Sample — Response (no sanction)
{
"response_code": 0,
"response_message": "Success"
}
Sample — Response (sanctioned)
{
"response_code": 0,
"response_message": "Success",
"value": {
"RESTRICT": {
"memberNo": 1234,
"nickname": "AUTONICK##9515338",
"banDay": 2,
"banCd": "B026",
"banStartDt": 1649053320000,
"banEndDt": 1649312520000
}
}
}
Troubleshooting
| Situation | Cause | Recommended handling |
|---|---|---|
| Callback event not received | The Callback URL isn't registered or a wrong URL is registered | Ask the STOVE technical contact to re-check the registered URL |
| Callback event not received | The STOVE NAT IP is blocked by the ACL/firewall | Check that the per-environment NAT IP is in the Inbound allow list |
| The same event received 3 times | The Callback API responds with something other than 200 (4xx/5xx/timeout) | Guarantee a 200 response even when the handling logic throws (idempotent handling) |
Lookup API response_code=90001 | The caller-id/Authorization header is missing or has a typo | Check that all three headers (Content-Type, Authorization, caller-id) are included |
Lookup API response_code=90001 | The path's guid/game_id doesn't match the token's permissions | Check the game_id permission when reissuing the API AccessToken |
| Duplicate Kick handling for one user | Multiple Callback instances handle the same event | We recommend dedup using the event_time+memberNo key |
| Kick persists even after the sanction is lifted | Lifting a sanction sends no separate event (expected behavior) | Re-call the lookup API at game entry to check the latest status |
Sample Code
This example is a reference implementation for the area the game server operates itself. Supplement the dependencies, exception handling, and logging to fit your actual environment.
1) Receiving the Callback API — Kick event handler
@RestController
@RequestMapping("/stove")
public class StoveKickController {
private final UserKickService kickService;
public StoveKickController(UserKickService kickService) {
this.kickService = kickService;
}
/** The endpoint that receives the Kick event called by the STOVE Event Broker */
@PostMapping(value = "/kick", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Map<String, Object>> onKickEvent(@RequestBody KickEvent event) {
if (!"GAME_RESTRICT".equals(event.getEventReason())) {
// Respond 200 to unknown events to prevent a resend loop.
return ResponseEntity.ok(Map.of("code", 0, "message", "IGNORED"));
}
kickService.kickAll(event.getTargetUsers(), event.getEventMessage(), event.getEventTime());
// Normal receipt → return 200 OK (on a non-200 response, the STOVE Event Broker resends up to 3 times)
return ResponseEntity.ok(Map.of("code", 0, "message", "SUCCESS"));
}
@Getter @Setter
public static class KickEvent {
@JsonProperty("event_reason") private String eventReason;
@JsonProperty("event_message") private String eventMessage;
@JsonProperty("event_time") private Long eventTime;
@JsonProperty("target_users") private List<Long> targetUsers;
}
}
2) Looking up user sanction info — REST call
@Component
public class StoveBanInfoClient {
private static final String HOST_LIVE = "https://api.onstove.com";
private static final String HOST_SANDBOX = "https://api.gate8.com";
private final RestClient restClient;
@Value("${stove.api.access-token}") private String apiAccessToken;
@Value("${stove.caller-id}") private String callerId;
@Value("${stove.profile:live}") private String profile;
public StoveBanInfoClient(RestClient.Builder builder) {
this.restClient = builder.build();
}
/** Look up sanction status. Returns Optional.empty() for a normal user. */
public Optional<BanInfo> getBanInfo(String guid, String gameId) {
String host = "sandbox".equalsIgnoreCase(profile) ? HOST_SANDBOX : HOST_LIVE;
String url = host + "/mmember/v1.0/signin/game/status/" + guid + "/" + gameId + "?guid_yn=Y";
BanResponse res = restClient.get()
.uri(url)
.header(HttpHeaders.AUTHORIZATION, "Bearer " + apiAccessToken)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.header("caller-id", callerId)
.retrieve()
.body(BanResponse.class);
if (res == null) return Optional.empty();
switch (res.responseCode) {
case 0: // Normal
return res.value != null ? Optional.ofNullable(res.value.RESTRICT) : Optional.empty();
case 10003: // No sanction info
return Optional.empty();
case 10122: // Sanctioned user
return res.value != null ? Optional.ofNullable(res.value.RESTRICT) : Optional.empty();
case 90001: // Token verification failed
throw new IllegalStateException("STOVE accessToken / caller-id / path mismatch — check settings");
default:
throw new IllegalStateException("Unexpected response_code: " + res.responseCode);
}
}
@Getter @Setter
public static class BanResponse {
@JsonProperty("response_code") public int responseCode;
@JsonProperty("response_message") public String responseMessage;
public ValueBlock value;
}
@Getter @Setter
public static class ValueBlock { public BanInfo RESTRICT; }
@Getter @Setter
public static class BanInfo {
public Long memberNo;
public String nickname;
public int banDay;
public String banCd;
public Long banStartDt;
public Long banEndDt;
}
}
3) Usage flow at the game-entry point
public class GameEntryService {
private final StoveBanInfoClient banClient;
private final GameSessionService sessionService;
public void onPlayerEnter(String guid, String gameId) {
banClient.getBanInfo(guid, gameId).ifPresentOrElse(
ban -> sessionService.kick(ban.memberNo, ban.banCd, ban.banEndDt),
() -> sessionService.allow(guid)
);
}
}