Buildml offloads code execution to a background queue so that the browser is never blocked waiting for a Docker container to finish running tests. When a user clicks Run or Submit, a tRPC mutation publishes a message to Upstash QStash, which then delivers it to this webhook for processing.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Praashh/buildml/llms.txt
Use this file to discover all available pages before exploring further.
Endpoint
Security
QStash signs every delivery with an HMAC using your signing keys. The webhook verifies the signature via the@upstash/qstash Receiver before any payload is parsed:
| Environment Variable | Purpose |
|---|---|
QSTASH_TOKEN | Authenticates the publisher (qstash.publishJSON) |
QSTASH_CURRENT_SIGNING_KEY | Verifies the most-recently-rotated QStash signature |
QSTASH_NEXT_SIGNING_KEY | Verifies the upcoming key during key rotation |
Message Types
The webhook supports two message types, determined by thetype field in the JSON body.
RUN
A RUN message is published bysubmission.run (tRPC mutation). It executes user code and stores the result in Redis for 10 minutes so the client can poll for it.
Payload:
Discriminator field. Must be the string
"RUN".A
crypto.randomUUID() UUID generated by the tRPC mutation. Used as the
Redis key suffix: run_result:{runId}.The Prisma
cuid of the problem being run. The webhook fetches the problem
record (including its parent problemSet) to resolve the executor slugs.The user’s Python source code to execute.
The authenticated user’s ID. Included for observability and future
per-user metrics.
SUBMIT
A SUBMIT message is published bysubmission.submit (tRPC mutation). It grades the code permanently and updates the Submission database row.
Payload:
Discriminator field. Must be the string
"SUBMIT".The Prisma
cuid of the Submission row created by the tRPC mutation before
publishing to QStash. The webhook fetches this record to retrieve the stored
code and problem context.The authenticated user’s ID.
Processing Flow
RUN Flow
Signature verification
The webhook reads the raw body and verifies the
upstash-signature header
using the QStash Receiver. A missing or invalid signature returns 401
immediately.Fetch problem from database
The webhook queries Prisma for the problem matching
problemId, including
the related problemSet, to obtain problem.slug and
problem.problemSet.slug.Call the executor
The webhook POSTs to
{EXECUTOR_URL}/execute with the user’s code and the
resolved slugs. The x-secret header authenticates the call.Parse executor response
The executor response is converted into a
status (PASS, FAIL, or
ERROR) and a human-readable output string. See Result Parsing below.SUBMIT Flow
Signature verification
Identical to the RUN flow — the QStash signature is verified before any
database work begins.
Fetch submission and problem from database
The webhook fetches the
Submission row (which contains the user’s stored
code) and eagerly includes the Problem and its parent ProblemSet.Call the executor
Same executor call as RUN — the code comes from
submission.code rather
than the message payload.Parse executor response
The executor response is converted to a final
status and output. See
Result Parsing below.Executor Request & Response
Request
Response
Number of test cases that passed.
Total number of test cases executed.
Per-test breakdown. Each entry has
name (string), passed (boolean), and
an optional error string describing the failure.Captured standard output from the user’s code.
Captured standard error output. Appended to the
output string if non-empty.Top-level error message if the executor itself failed (e.g. syntax error,
timeout). When set,
status is forced to ERROR regardless of test counts.Result Parsing
The webhook maps the executor response to three possible statuses:| Status | Condition |
|---|---|
PASS | result.passed === result.total and result.total > 0 |
FAIL | Any tests failed (passed < total) |
ERROR | result.error is set, or the executor returned a non-2xx HTTP status |
output string is assembled as follows:
- First line is always
{passed}/{total} tests passed - Each test result follows as
✓ {name}(pass) or✗ {name}: {error}(fail) - If
result.erroris set, it is appended after a blank line - If
result.stderris non-empty, a--- stderr ---section is appended
Error Handling
If any step throws — database lookup fails, executor is unreachable, or the executor returns a non-2xx status — the webhook catches the error and writes anERROR status so the client is never left polling indefinitely.
For SUBMIT errors, the Submission row is updated:
status: "ERROR" (still with the 600-second TTL):
500 in the error case so QStash can retry delivery according to its retry policy.
Polling for Results
After receiving arunId or submissionId from the tRPC mutation, clients poll the submission.getStatus query:
getStatus returns { status: "PENDING", output: null } when the Redis key does not yet exist, so the client can distinguish “not started” from “processing” from “done”.