Skip to main content

Verified Parent Linking (VPL)

Verified parent linking (VPL) lets a player invite their own parent into your product. The player sends the invite, the parent verifies their identity, and from then on the parent can see and set that player's controls. Nothing in your product is blocked while the invite is outstanding, and nothing breaks if the parent never accepts.

This guide walks through the whole flow: creating the session, sending the invite, tracking its state, canceling it, and handling an unlink from either side.

Read this first

Verified parent linking (VPL) explains why VPL is a link rather than a gate, and why it's mutually exclusive with VPC. The eligibility rules described there decide whether the calls in this guide succeed.

Prerequisites

Before you begin, you'll need:

  1. A k-ID product: Create and configure your product in Compliance Studio.
  2. An API key: Generate one from the Developer Settings page of your product. All calls in this guide are server to server. See Authentication.
  3. A webhook endpoint: An HTTPS endpoint to receive Challenge.StateChange and Session.Unlink. See Webhooks.
  4. A subscription to the events you handle: An endpoint receives only the event types it's subscribed to, and k-ID drops the rest without a delivery attempt or an error. On the product's Developer Settings page in Compliance Studio, select Challenge.StateChange and Session.Unlink for your endpoint.
  5. VPL enabled for your organization: Please contact k-ID to have this capability turned on for your organization.

All examples use the live base URL https://game-api.k-id.com/api/v1, Content-Type: application/json, and an Authorization: Bearer <api-key> header. In test mode, use https://game-api.test.k-id.com/api/v1.

Step 1: Create the player's session

Call POST /age-gate/check to create a session for the player.

POST /age-gate/check
{
"jurisdiction": "US-CA",
"age": 17
}

Pass at most one of age, dateOfBirth, or kuid (for a returning player whose kuid you cached). Passing more than one returns a 400. See /age-gate/check for the other age signals the endpoint accepts.

A successful response carries the session:

{
"status": "PASS",
"session": {
"sessionId": "b1a6482d-5242-4b4a-aa88-3fa52595a672",
"status": "ACTIVE",
"managedBy": "PLAYER",
"hasApproverEmail": false
}
}

Store the sessionId against your own user record. Every call in this guide is keyed on it, and it stays the same across link, unlink, and relink.

If the status is CHALLENGE instead of PASS, the player is below the access age for their jurisdiction and needs parental consent. Route them through VPC and don't call the invite endpoint, because no session exists to invite a parent to.

A session that came back PASS can still be ineligible for VPL if it carries a GUARDIAN-managed permission. Check permissions[] on the session before you show the invite option, or handle the FEATURE_DISABLED error in Step 3.

Step 2: Show the invite option

Put the option wherever it fits your product, such as settings, the player's profile, or onboarding. When the player taps it, collect the parent's email address.

Because VPL is opt-in and non-blocking, this is a normal piece of product surface rather than an interstitial. The player can ignore it indefinitely.

Step 3: Send the invite

Call POST /challenge/invite-parent.

POST /challenge/invite-parent
{
"sessionId": "b1a6482d-5242-4b4a-aa88-3fa52595a672",
"parentEmail": "parent@example.com",
"playerName": "Alex",
"locale": "en"
}
FieldRequiredNotes
sessionIdyesThe session from Step 1
parentEmailyesWhere the invite is sent
playerNamenoThe player's display name, 1 to 63 characters. Shown to the parent in the invite email. Omit it and the parent sees a generic placeholder
localenoIETF BCP 47 tag for the invite email. Omit it and k-ID uses the parent's own stored language if it already knows them, then your product's primary language, then English
{
"status": "CHALLENGE",
"challenge": {
"challengeId": "ae6d4729-af32-42ea-8ef2-ff46c7664802",
"type": "CHALLENGE_PARENT_INVITE"
}
}

One call creates the challenge, binds it to the session, and sends the email. There's no follow-up call to trigger the email, and /challenge/send-email rejects parent-invite challenges.

Store the challengeId next to the session. It identifies this invite in the webhook, in the status endpoint, and in the cancel call.

While you hold a challengeId for a pending invite, avoid sending another invite to a different email for the same session.

How long the link lasts

The link in the invite email is valid for 14 days in live mode, and for 7 minutes in test mode so you can exercise expiry without waiting.

Errors to handle

Errors return HTTP 400 with the code in the response body's error field.

CodeWhenWhat to do
FEATURE_DISABLEDVerified parent linking is not enabled. VPL is off for your organizationContact k-ID
FEATURE_DISABLEDVerified parent linking is not available for this player. The player is below the access age, or the session carries a GUARDIAN-managed permissionFall back to VPC. Don't show the invite option for this session
NOT_FOUNDThe session doesn't existRecheck the sessionId you stored
INVALID_INPUTThe session's jurisdiction isn't configured on your productFix the product configuration in Compliance Studio
INVALID_EMAILSession is not eligible for a new invite. The session already has a linked parentShow the current link state instead

Step 4: Wait, without blocking

Keep your product fully available while the invite is outstanding. The player already passed the age gate, and nothing about the invite changes what they can do.

When the parent completes the flow, k-ID fires Challenge.StateChange to your webhook endpoint:

{
"eventType": "Challenge.StateChange",
"data": {
"id": "ae6d4729-af32-42ea-8ef2-ff46c7664802",
"productId": 12345,
"type": "CHALLENGE_PARENT_INVITE",
"status": "PASS",
"sessionId": "b1a6482d-5242-4b4a-aa88-3fa52595a672",
"approverEmail": "parent@example.com",
"kuid": "12b9fa0e-6d6d-4903-a1fc-f2233027b71d"
}
}

On receipt:

  • Match data.id and data.sessionId back to your stored player record.
  • Cache the kuid. This is what tells you that a parent is linked to this session.
  • Update your UI to show that a parent is connected.

The same event fires with status: FAIL when the invite is canceled, but a FAIL payload carries no sessionId. Match it on data.id against the challengeId you stored, not on data.sessionId, or your handler drops every cancel. Treat it as no parent being connected. The player can send another invite.

If the parent never accepts, no further event arrives and nothing changes.

Step 5: Check the current state on-demand

The webhook is the source of truth for transitions, but you'll often need the current state without waiting for one, such as when the player reopens your app.

Is a parent linked now?

Call GET /session/get with the sessionId and read two fields:

  • hasApproverEmail is true while a parent is linked, and false otherwise. This is the signal to branch on.
  • kuid identifies the child profile the session is attached to.

What happened to the last invite?

Call GET /challenge/get-status with the stored challengeId.

StatusMeaningSuggested UI
PENDINGThe invite exists and the email was sent, but the parent hasn't opened itShow the invite as pending, naming the parentEmail
IN_PROGRESSThe parent opened the link and started the flowShow the invite as pending, naming the parentEmail
PASSThe parent finished, and the session is linkedShow the parent as connected
FAILThe invite was canceled or otherwise endedShow no parent as connected, and offer another invite

Putting it together

When you render the player's profile:

  1. Call /session/get. If hasApproverEmail is true, show "Parent connected" and offer the unlink option from Step 7.
  2. Otherwise call /challenge/get-status with your most recent challengeId:
    • PENDING or IN_PROGRESS: show the invite as pending and offer cancel.
    • PASS while /session/get still shows no parent: a brief race. Treat it as pending and check again shortly.
    • FAIL, or no stored challengeId: show the invite option.

Step 6: Cancel a pending invite

If the player wants to revoke an invite while the parent is still mid-verification, call POST /challenge/cancel-invite.

POST /challenge/cancel-invite
{
"challengeId": "ae6d4729-af32-42ea-8ef2-ff46c7664802"
}
{ "cancelled": true }

The challenge moves to FAIL with failureReason: "cancelled-by-initiator", you receive a Challenge.StateChange with status: FAIL, and a parent already looking at the verification widget sees the canceled state.

Canceling an already-canceled invite is safe and returns cancelled: true again.

CodeWhen
NOT_FOUNDThe challenge doesn't exist for this product
INVALID_INPUTInvalid challenge type. Only parent-invite challenges can be canceled here
INVALID_INPUTInvite already accepted. The parent finished before the cancel arrived
FEATURE_DISABLEDVPL is off for your organization

After a FAIL, the session accepts a fresh invite right away.

Either side can end the relationship at any time. The parent can unlink from Family Connect, and you can offer the player an unlink option in your product.

An unlink returns the session to its unattached state: the kuid and the parent link are cleared, and the allowances and controls the parent set are removed. The session itself stays ACTIVE, and the player's permissions and pushed data is left intact. The player carries on with the same sessionId.

POST /session/unlink-parent
{
"sessionId": "b1a6482d-5242-4b4a-aa88-3fa52595a672",
"locale": "en"
}
{
"unlinked": true,
"unlinkedAt": "2026-04-20T18:42:11Z"
}

unlinked is true when this call, or a previous one, ended a parent link. It's false when the session never had a linked parent, and unlinkedAt is then omitted. locale sets the language of the notification email k-ID sends the parent to tell them the link ended. Omit it and k-ID uses the parent's own stored language if it already knows them, then your product's primary language, then English.

The call is idempotent on sessionId. A second call after the link is already gone returns 200 with the original unlinkedAt, not the time of the repeat call.

CodeWhen
NOT_FOUNDThe session doesn't exist
INVALID_INPUTThe session's jurisdiction isn't configured on your product
FEATURE_DISABLEDUnlink is only available for VPL-linked sessions. The session isn't on the VPL path, or VPL is off for your organization

k-ID fires Session.Unlink whenever a link ends, from either side.

{
"eventType": "Session.Unlink",
"data": {
"sessionId": "b1a6482d-5242-4b4a-aa88-3fa52595a672",
"productId": 12345,
"unlinkedBy": "player",
"unlinkedAt": "2026-04-20T18:42:11Z"
}
}

unlinkedBy is player when the unlink came through your API, and parent when the parent unlinked from Family Connect.

On receipt, stop showing the parent as connected. The player can send a new invite whenever they want.

What's next?

Now that verified parent linking is wired up, explore these resources to go deeper:

  • Verified parent linking (VPL): The concept behind this guide: link versus gate, and why VPL and VPC are mutually exclusive
  • Sessions: How the sessionId and kuid behave across link, unlink, and relink
  • Session.Unlink: The webhook that tells you when either side ends a link
  • Webhooks: Complete guide to webhook subscription, delivery, and signature validation
  • Pre-launch checklist: Review requirements before going live