Skip to main content
Every Edpire integration is one of two shapes. The difference is not what the assessment contains, it is who draws the screen.

Exam Hall

We draw the screen. You mount one component and the learner sees the whole paper, navigates it, submits it, and gets graded feedback.Ships in a day. Difficulty 3/10.

Arcade

You draw the screen. We hand you one question at a time and grade it on demand. Hearts, streaks, timers, pacing, all yours.Ships in a week. Difficulty 6/10.
The same assessment can be delivered either way. Nothing about the content, the authoring, the results or the webhooks changes between them. You can also ship both, in the same app, against the same catalogue. That is what Edulylo does: exams and competitive entrance tests run in Exam Hall, practice exercises run in Arcade.

Which one

Answer one question honestly:
Is the shape of the learner experience part of your product, or just the container for someone else’s content?
If a learner would describe your practice mode as a feature of your app (“the one with the streaks”), that is Arcade. If they would describe it as “the test”, that is Exam Hall.
Start with Exam Hall even if Arcade is your end goal. It proves the token flow, the origin allow-list and the webhook, which are the parts most likely to surprise you. Arcade then reuses all three unchanged.

Before either mode

Three things, once. This is the part you cannot do alone, so start it first.
1

Create your organisation and key

Sign up at edpire.com, create your organisation, then open Integrations and create an API key.On the same screen, add every origin your player will be embedded on, including staging and local development:
Origins are matched exactly, scheme and host and port. https://app.yourplatform.com does not cover https://www.app.yourplatform.com. A missing origin is the single most common cause of a blank player on launch day, so send the full list up front.
Mobile apps in a WebView send no origin, or an opaque one like capacitor://localhost. Both are handled. You do not need to register anything for native.
2

Install, or do not

On React 18 or 19, install the package:
On anything else, skip this step entirely. Angular, Vue, Svelte, Rails, Django, plain HTML, or React 15, 16 and 17 all use a single script tag instead, with no npm and no build step. See Not on React below. The rest of this guide applies unchanged, only the mounting syntax differs.Styles are bundled and scoped, so the SDK cannot leak CSS into your app and your Tailwind version does not matter.
3

Store one ID per item

Add a nullable edpire_assessment_id column to whatever table holds your lessons, tests or exercises. That UUID is the entire contract between your catalogue and ours.Your content team authors in Edpire. List assessments by title in your own admin and store the ID against your lesson. See Find an assessment ID, and catalogue sync for keeping the list current.

Exam Hall

The learner opens your page and sees the complete assessment: every question, a progress indicator, reading passages in a side panel where the assessment has them, and a submit button. When they submit, it grades and shows per question feedback without a page load.

Step 1. Mint tokens on your server

Your API key never touches the browser. Instead your backend issues a short lived token scoped to one learner and one assessment.
app/api/edpire/token/route.ts
That is the whole endpoint. It works on any framework via toNodeHandler().
This endpoint is reachable on its own, so if some of your content is paid or restricted, check entitlement here, not only on the page that renders the player. Add a resolveAssessmentId callback that looks the ID up in your own catalogue and throws if the learner is not entitled to it. A page level check is UX. This one is the boundary.

Step 2. Mount the player

The player fills its container, so a container with no height collapses to nothing. Always set className or style with a real height. h-screen, h-[600px], or a flex parent all work.
Useful props:

Step 3. Give the learner somewhere to go

The player grades in place and then stops. Without a next step the learner is stranded on a finished exam. Two options, and you should use both:
  1. Your own result screen, navigated to from onComplete. This is the primary path, because the useful next action belongs to you: retry, next lesson, back to the class.
  2. returnUrl as a fallback, so the built in report button also lands somewhere sensible.

Step 4. Record the result

See Results, for both modes below. Done. That is Exam Hall in full.

Arcade

The learner sees one question, answers it, gets told immediately whether they were right, and moves on. You own everything around the question: the progress bar, the lives, the streak counter, the celebration animation, the pacing. Edpire gives you two things here. A renderer that turns question content into a working interactive question, and a grader you can call per question.

Step 1. Fetch the content on your server

Server side, because the key stays there. Cache it, question content rarely changes.

Step 2. Flatten it into steps

An assessment nests questions inside exercises. Arcade wants a flat list.
Import pure helpers from @edpire/sdk/core, not @edpire/sdk. The main entry pulls in React and the full player tree, which a server file cannot import. /core is the same functions with none of that.

Step 3. Render one question

EdpireQuestion is deliberately unstyled beyond what makes the interaction work. It inherits your fonts and colours, and your CSS reaches into it on purpose. Style it to match your product.

Step 4. Grade the question

Pass feedback straight back into EdpireQuestion and the right and wrong states appear on the correct blanks, choices and pairs. You do not have to interpret it.
/check allows 3 checks per question, per session, per rolling hour. That is an anti brute force limit, since include_correct_answers would otherwise let a learner guess their way to the answer key. Generate a fresh session_id per attempt, and design the loop so one check per question is the normal case.
Proxy this through your own backend rather than calling Edpire from the browser, and validate that the assessment is one you actually publish before forwarding. Otherwise any signed in user can grade against any assessment ID they can guess.

Step 5. Submit the attempt

Per question checks do not create a submission. When the learner finishes, send the whole attempt once so it lands in reporting and fires your webhook.

Step 6. Record the result

Same as Exam Hall. Read on.

Results, for both modes

A result reaches you two ways, and you want both.
1

Webhook, the primary path

Register an endpoint and we POST to it when a submission completes. Verify the signature with EDPIRE_WEBHOOK_SECRET. See Webhooks.Handle it idempotently. Retries are real, and a delivery can arrive twice.
2

Client reconciliation, the safety net

onComplete hands you the submission ID the moment grading finishes. Take it, re-fetch the submission server to server, confirm the learner_ref matches the session, and write the score.This is what saves you when a webhook is delayed or lost. Without it a dropped delivery leaves the attempt stuck as pending forever, and the learner sees a blank result screen.
Both paths should funnel into one function, guarded by a check on whether that submission is already recorded. Then it does not matter which one wins the race.
Match the attempt by its own ID. Do not “find the most recent pending row for this learner”, which quietly completes the wrong attempt the moment someone has two open in different tabs. We have shipped that bug. It is not fun to find.
If an assessment contains open response questions, the first result is provisional. Check is_fully_graded and wait for submission.grading.completed before showing a score as final.

Not on React

Both modes work without React, without npm and without a build step. The CDN bundle carries its own React inside a closure, so your framework and your React version are irrelevant to it. It does not touch window.React, so it cannot collide with a React app that already exists on the page.
mount() returns an object with unmount(), which is what you call from your framework’s teardown hook: ngOnDestroy, onUnmounted, onDestroy, or a router leave guard. For Arcade without React, EdpireSDK.renderQuestion() is the imperative equivalent of <EdpireQuestion>. It returns an instance with setContent(), setFeedback() and unmount(), so you drive it from your own loop:
EdpireSDK.flattenAssessment and EdpireSDK.buildSubmitPayload are on the same global.
The bundle is roughly 700 KB gzipped, because it contains React, the maths renderer and the rich text engine. That is the price of needing no build step. Load it on the page that runs the assessment, not in your global layout, and prefer a pinned version over @latest in production so a release cannot change under you.

React version support


How hard is this, honestly

Exam Hall: 3 out of 10. One server endpoint you can copy verbatim, one component, one webhook handler. A competent React developer does it in an afternoon. The only genuinely fiddly parts are remembering to size the container and getting your origins registered before you test. Arcade: 6 out of 10. The Edpire specific parts are still easy. flattenAssessment, EdpireQuestion and /check are maybe a day. The other four to seven days are you building a game: state machine, progress, lives, animations, resume behaviour, what happens when they close the tab mid run. That work is real, but it is your product, not our API. What actually consumes the time, in order: Being on Angular, Vue or anything else does not push the number up. The script tag path is about as much work as the React one, and arguably less, since there is no install and no build config. What does push it up: you need file or audio answers, so you must supply a mediaHandler and somewhere to store the files, or you have no public HTTPS endpoint for webhooks yet, or your backend is not Node, in which case @edpire/sdk/client will not run for you and you call the REST API directly instead. The token endpoint is a single POST, so this is a small amount of work in any language. Things that push it down: you are on Next.js, your learners already have stable user IDs, and your content is authored before you start writing code.

Before you go live

  • Every origin registered, including staging and localhost
  • Entitlement checked inside the token endpoint, not only on the page
  • Player container has a real height
  • mediaHandler supplied, if any question takes an upload or a recording
  • locale set from the assessment’s language, not your UI language
  • Webhook signature verified, handler idempotent
  • Client reconciliation matches the attempt by ID
  • Learner has a next step after submitting
  • Open response assessments show provisional scores as provisional
  • Tested with a real learner account, on mobile, on a slow connection
  • If using the script tag, pinned to an exact SDK version rather than @latest

Where to go next

Integration Runbook

Who does what, in what order, with dates.

Embedded Player

Every Exam Hall prop and callback.

Custom Flow

Arcade in full detail, with a complete worked example.

Troubleshooting

Blank player, token errors, styles behaving oddly.