Create a hosted verification form
Register a plugin handler, create a short-lived form, and validate its submission.
A plugin can create a one-time hosted form and receive its submission inside the agent JVM. Use this for plugin-owned forms, then apply your own authorization policy.
For game-account sign-in and tokens, use Game OAuth. A hosted form does not issue an OAuth token or independently prove a player's Microsoft identity.
Before you begin
You need a supported game agent, its plugin API dependency, and a plugin lifecycle that manages coroutine work and shutdown. Challenge creation needs a live authenticated agent connection.
The API is experimental. Kotlin callers must opt in. Java callers must account for the Kotlin suspend-function boundary; the example below uses Kotlin directly.
Register and create a form
The following class is complete against the current API. Call begin() from your plugin's coroutine scope after the agent loads. Give the returned URL to the intended player through your platform's messaging API.
import com.enderdash.agent.api.EnderdashApi
import com.enderdash.agent.api.ExperimentalEnderdashApi
import com.enderdash.agent.api.verification.VerificationChallengeCreateRequest
import com.enderdash.agent.api.verification.VerificationChallengeValidationResult
import com.enderdash.agent.api.verification.VerificationFieldSpec
import com.enderdash.agent.api.verification.VerificationFieldType
import java.util.concurrent.ConcurrentHashMap
@OptIn(ExperimentalEnderdashApi::class)
class HostedVerification(private val api: EnderdashApi) : AutoCloseable {
private val pending = ConcurrentHashMap<String, String>()
private val registration = api.verification.registerValidationHandler("example-plugin") { event ->
val expectedPlayer = pending[event.challengeId]
val response = event.fields["reply"]?.values?.singleOrNull()?.trim()
when {
expectedPlayer == null || event.playerUuid != expectedPlayer ->
VerificationChallengeValidationResult.rejected("unknown_challenge", "Request not found")
!event.risk.hcaptchaPassed ->
VerificationChallengeValidationResult.rejected("captcha_failed", "Complete the captcha")
response.isNullOrEmpty() || response.length > 100 ->
VerificationChallengeValidationResult.rejected("invalid_reply", "Enter 1 to 100 characters")
else -> {
pending.remove(event.challengeId)
VerificationChallengeValidationResult.accepted("Response received")
}
}
}
suspend fun begin(playerUuid: String): String {
val challenge = api.verification.createChallenge(
VerificationChallengeCreateRequest(
pluginId = "example-plugin",
title = "Send a response",
playerUuid = playerUuid,
fields = listOf(
VerificationFieldSpec(
id = "reply",
type = VerificationFieldType.TEXT,
label = "Response",
minLength = 1,
maxLength = 100,
),
),
expiresInSeconds = 300,
),
)
pending[challenge.challengeId] = playerUuid
return challenge.url
}
override fun close() {
registration.close()
pending.clear()
}
}This example validates a form response. It does not grant privileges. In a real plugin, also check your local operation state, expiration, and correlation before applying any protected change. Remove expired entries from the pending map.
Close the class from your plugin's shutdown callback and cancel plugin-owned coroutine work. Register only one handler per plugin ID. Creation can throw when signaling is unavailable or the request is rejected.
Verify the complete flow
- Register the handler after retrieving the active API provider.
- Create a challenge for a test player and open its returned URL.
- Complete hCaptcha and submit a short response.
- Confirm the handler accepted the expected challenge.
- Check the server's Verification panel for the result and decision code.
- Test an invalid response, an expired challenge, and plugin shutdown before allowing protected operations.
A null handler result or a thrown exception rejects the submission as unhandled. Always return an explicit accepted or rejected result.
Form limits
A challenge contains 1 to 8 fields. Its default lifetime is 300 seconds; the maximum is 900 seconds.
| Type | Options |
|---|---|
| Text | Placeholder, minimum and maximum length; up to 500 characters |
| Number | Placeholder, minimum and maximum value |
| Select | Placeholder and 1 to 20 choices |
| Checkbox | Required flag and label |
Field IDs are the keys in the handler's submitted-field map. Validate required fields again in the handler instead of relying only on browser validation.
Risk metadata and cancellation
The event includes hCaptcha status and network classifications such as proxy, hosting, and mobile. Geographic enrichment is best effort and nullable. Treat those classifications as policy inputs, not a complete trust decision.
Cancel a pending challenge with api.verification.cancelChallenge(challengeId). A true result means the control plane accepted the cancellation. False can mean the challenge completed, expired, or was unknown.
Next: API lifecycle and lookups or Game OAuth for identity sign-in.
Was this page helpful?
Send a quick note if anything is missing or unclear.
Last updated on