- Sphere Engine overview
- Compilers
- Overview
- API integration
- JavaScript widget
- Problems
- Overview
- API integration
- JavaScript widget
- E-learning platforms
- Problem setter's handbook
- Problems archive
- RESOURCES
- Programming languages
- Submission streams
- Webhooks
- Disk operations
- Multi-file submissions
- Generating images
- Client libraries
- API Changelog
- FAQ
Note: The webhook mechanism is not available for the new Sphere Engine Containers module yet. The release of webhook integration for the new module is planned for the first quarter of 2023.
Our implementation of the webhook mechanism has been designed to deliver a reliable and efficient method for event communication between Sphere Engine components. When the execution of the submission finishes, our system instantly sends a message about it to the client's system. Think of it as push notifications from social media services that go directly to your web browser.
The following diagram depicts the idea is a very simple way:

What are webhooks and how to receive them?
Webhook is nothing more than an HTTP request that goes from a service (Sphere Engine) to the client's application. Note that the communication flow in the case of webhooks works opposite to the usual one in which the client is a side that sends the request.
Receiving webhooks consists of:
- publishing an endpoint to send requests to (e.g.
your_awesome_site.com/your_app/webhook_endpoint
), - receiving and processing incoming requests inside your application.
You can receive webhooks from all Sphere Engine components:
- Compilers API
- Compilers Widget
- Problems API
- Problems Widget
- Problems LTI tool
- (coming soon!) Containers API
- (coming soon!) Containers Workspaces
Configuring a webhook in Sphere Engine Dashboard
The webhook configuration is global for all Sphere Engine components. Go to Dashboard
> Webhooks
and provide the required information. For example:

Webhook URL
is the space for a complete URL; use this web address to send requests to- Choose which notifications you want to subscribe to; requests from all modules are sent to the same webhook endpoint, but you will be able to distinguish between them using the information from the message body
Webhook message specification
Webhook message is an HTTP POST request that goes from Sphere Engine to the client's webhook endpoint. The body of the request contains valid JSON data with its core unified for all Sphere Engine components.
Message core
Name | Type | Description |
---|---|---|
origin | string |
The identifier of the system that can take the following values:
|
timestamp | integer | The Unix timestamp generated when the message is created |
submission | object | Information about the submission |
widget (present only for origin values: secw, sepw, se4e) | object | Additional context related to execution within a widget |
The structure the submission
object (see Message core above) for Compilers API (origin=sec) and Compilers Widget (origin=secw)
Name | Type | Description |
---|---|---|
id | integer | submission id |
executing | boolean | indicates whether a submission is being executed |
date | string | date and time of submission creation [yyyy-mm-dd hh:mm:ss TZD] note that server time is used |
compiler object | ||
compiler.id | integer | compiler id |
compiler.name | string | compiler name |
compiler.version.id | integer | compiler version id |
compiler.version.name | string | compiler version name |
result object | ||
result.status.code | integer | status code (see section "Submission status") |
result.status.name | string | status name |
result.time | float | execution time [seconds] |
result.memory | integer | memory consumed by the program [kilobytes] |
result.signal.code | integer | signal raised by the program |
result.signal.name | string | description of the raised signal |
The structure the widget
object (see Message core above) for Compilers Widget (origin=secw)
Name | Type | Description |
---|---|---|
hash | string | widget identifier |
submission.id | integer |
submission id in the widget context; note that it's not the same id as for submission object |
custom_data | string |
custom data provided during widget initialization in data-custom-data parameter |
The structure the submission
object (see Message core above) for Problems API (origin=sep), Problems Widget (origin=sepw) and Problems LTI tool aka Sphere Engine For Education (origin=se4e)
Name | Type | Description |
---|---|---|
id | integer | submission id |
executing | boolean | indicates whether a submission is being executed |
date | string | date and time of submission creation [yyyy-mm-dd hh:mm:ss TZD] note that server time is used |
compiler object | ||
compiler.id | integer | compiler id |
compiler.name | string | compiler name |
compiler.version.id | integer | compiler version id |
compiler.version.name | string | compiler version name |
problem object | ||
problem.id | integer | problem id |
problem.code | string | [deprecated] problem code |
problem.name | string | problem name |
result object | ||
result.status.code | integer | status code (see section "Submission status") |
result.status.name | string | status name |
result.score | float | final score |
result.time | float | execution time [seconds] |
result.memory | integer | memory consumed by the program [kilobytes] |
result.signal.code | integer | signal raised by the program |
result.signal.name | string | description of the raised signal |
result.testcases[].number | integer | test case number |
result.testcases[].status.code | integer | test case status code (see section "Submission status") |
result.testcases[].status.name | string | test case status name |
result.testcases[].score | float | test case score |
result.testcases[].time | float | test case execution time [seconds] |
result.testcases[].memory | integer | test case memory consumed by the program [kilobytes] |
result.testcases[].signal | integer | test case signal raised by the program |
result.testcases[].signal_desc | string | description of the raised test case signal |
The structure the widget
object (see Message core above) for Problems Widget (origin=sepw)
Name | Type | Description |
---|---|---|
hash | string | widget identifier |
submission.id | integer | submission id in the widget context |
submission.grade | float | submission grade |
custom_data | string |
custom data provided during widget initialization in data-custom-data parameter |
user object | ||
user.name | string | user name provided on the welcome page |
user.email | string | user email address provided on the welcome page |
user.uuid | string | user identifier generated by the Sphere Engine system |
user.id | string |
user identifier provided during widget initialization (user-id parameter) |
The structure the widget
object (see Message core above) for Problems LTI tool aka Sphere Engine For Education (origin=se4e)
Name | Type | Description |
---|---|---|
consumer_key | string | consumer key from LTI integration settings |
submission.id | integer | submission id in the widget context |
submission.grade | float | submission grade |
custom_data | string |
custom data provided during widget initialization in data-custom-data parameter |
user object | ||
user.name | string | username delivered from LMS; it can be null for strict privacy LMS settings |
user.email | string | user email delivered from LMS; it can be null for strict privacy LMS settings |
user.uuid | string | user identifier generated by the Sphere Engine system |
user.id | string | user identifier generated by LMS |
LTI protocol-specific parameters | ||
tool_consumer_instance_guid | string |
parameter tool_consumer_instance_guid from LTI protocol; it can be null |
context_id | string |
parameter context_id from LTI protocol; it can be null |
resource_link_id | string |
parameter resource_link_id from LTI protocol |
Webhook messages examples
{
"origin": "sec",
"timestamp": 1610636162,
"submission": {
"id": 167124381,
"executing": false,
"date": "2021-01-14 14:55:56 +00:00",
"compiler": {
"id": 29,
"name": "PHP",
"version": {
"id": 5,
"name": "php 7.4.3"
}
},
"result": {
"status": {
"code": 15,
"name": "accepted"
},
"time": 0.01,
"memory": 51824,
"signal": {
"code": 0,
"name": ""
}
}
}
}
{
"origin": "sep",
"timestamp": 1610636162,
"submission": {
"id": 15778763,
"executing": false,
"date": "2021-01-14 14:55:56 +00:00",
"compiler": {
"id": 29,
"name": "PHP",
"version": {
"id": 5,
"name": "php 7.4.3"
}
},
"problem": {
"id": 170,
"code": "SETEST",
"name": "Life, the Universe, and Everything"
},
"result": {
"status": {
"code": 15,
"name": "accepted"
},
"score": 100,
"time": 0.01,
"memory": 52488,
"signal": 0,
"signal_desc": "",
"testcases": [
{
"number": 0,
"status": {
"code": 15,
"name": "accepted"
},
"score": 0,
"time": 0.01,
"memory": 52488,
"signal": 0,
"signal_desc": ""
}
]
}
}
}
{
"origin": "secw",
"timestamp": 1610636162,
"submission": {
"id": 167137007,
"executing": false,
"date": "2021-01-14 14:55:56 +00:00",
"compiler": {
"id": 29,
"name": "PHP",
"version": {
"id": 5,
"name": "php 7.4.3"
}
},
"result": {
"status": {
"code": 15,
"name": "accepted"
},
"time": 0.01,
"memory": 82560,
"signal": {
"code": 0,
"name": ""
}
}
},
"widget": {
"hash": "a532aa0f61da800f8288c77588a29cd5",
"submission": {
"id": 1151666
},
"custom_data": null
}
}
{
"origin": "sepw",
"timestamp": 1610636162,
"submission": {
"id": 15780466,
"executing": false,
"date": "2021-01-14 14:55:56 +00:00",
"compiler": {
"id": 29,
"name": "PHP",
"version": {
"id": 5,
"name": "php 7.4.3"
}
},
"problem": {
"id": 170,
"code": "SETEST",
"name": "Life, the Universe, and Everything"
},
"result": {
"status": {
"code": 15,
"name": "accepted"
},
"score": 100,
"time": 0.01,
"memory": 52488,
"signal": 0,
"signal_desc": "",
"testcases": [
{
"number": 0,
"status": {
"code": 15,
"name": "accepted"
},
"score": 0,
"time": 0.01,
"memory": 52488,
"signal": 0,
"signal_desc": ""
}
]
}
},
"widget": {
"hash": null,
"submission": {
"id": 4458436,
"grade": 0
},
"user": {
"name": null,
"email": null,
"uuid": null,
"id": null
},
"custom_data": null
}
}
{
"origin": "se4e",
"timestamp": 1610636162,
"submission": {
"id": 15781550,
"executing": false,
"date": "2021-01-14 14:55:56 +00:00",
"compiler": {
"id": 29,
"name": "PHP",
"version": {
"id": 5,
"name": "php 7.4.3"
}
},
"problem": {
"id": 170,
"code": "SETEST",
"name": "Life, the Universe, and Everything"
},
"result": {
"status": {
"code": 15,
"name": "accepted"
},
"score": 100,
"time": 0.01,
"memory": 52488,
"signal": 0,
"signal_desc": "",
"testcases": [
{
"number": 0,
"status": {
"code": 15,
"name": "accepted"
},
"score": 0,
"time": 0.01,
"memory": 52488,
"signal": 0,
"signal_desc": ""
}
]
}
},
"widget": {
"consumer_key": null,
"tool_consumer_instance_guid": null,
"context_id": null,
"resource_link_id": null,
"submission": {
"id": 4458446,
"grade": 0
},
"user": {
"name": null,
"email": null,
"uuid": null,
"id": null
},
"custom_data": null
}
}
Acknowledging webhooks
Sphere Engine service keeps a webhook message for a while to make sure that the client has received it properly. The elementary rule that the webhook endpoint should implement is that each webhook is an HTTP POST request that expects a specific response:
- respond with the HTTP status code 200 to acknowledge receiving and processing the webhook message,
- any other HTTP response code will be interpreted as a failure and webhook message will not be acknowledged.
Retry schedule in the case of failures
Sometimes, the first delivery attempt of the webhook message fails. It can be caused by multiple factors. Just to name a few:
- Network failure between client's webhook endpoint and Sphere Engine service
- Unexpected failure on the Sphere Engine side
- Unexpected failure on the client's application side
- Unacknowledged webhook message (i.e. non-200 HTTP status code in the response)
To make sure that the webhook message does not get completely lost, we defined a retry schedule. For each message, we will do up to 15 additional delivery attempts. Each attempt is performed with a delay that grows with the total number of attempts according to the table:
# | Delay |
---|---|
1 | 1 second |
2 | 3 seconds |
3 | 9 seconds |
4 | 16 seconds |
5 | 32 seconds |
6 | 1 minute |
7 | 5 minutes |
8 | 15 minutes |
9 | 45 minutes |
10 | 2 hours |
11 | 4 hours |
12 | 8 hours |
13 | 12 hours |
14 | 12 hours |
15 | 12 hours |
16 | 12 hours |
There are more than two days for a webhook message to be properly delivered. After a series of 16 failure attempts, the webhook message is lost and will not be delivered.
Testing webhooks without endpoint
If you simply want to test the features, reliability, or behavior of the webhooks you can do it without an endpoint on your side. Such services as https://webhook.site or https://requestbin.com/ allow you to generate a unique custom URL that you can use as your webhook endpoint for testing purposes.
Implementing webhook endpoint
It is possible to implement a webhook endpoint using any technology that can handle HTTP protocol. You can do it as a standalone application as well as part of a bigger one. You can use a web framework or create everything from scratch.
Here is a simple PHP script that implements an elementary webhook endpoint capable of receiving incoming messages and storing them in a messages.dat
file.
<?php
file_put_contents('messages.dat', 'HEADERS: ' . serialize($_REQUEST) . PHP_EOL, FILE_APPEND | LOCK_EX);
$requestBody = file_get_contents("php://input");
file_put_contents('messages.dat', 'DATA: ' . $requestBody . PHP_EOL, FILE_APPEND | LOCK_EX);
http_response_code(200); // response code 200 to acknowledge
Note: In the example, you need to read standard input data to fetch request body data. If you use a web application framework (e.g. Symfony, Django, Laravel) it is very likely that there is a dedicated method that helps you get raw body data from POST requests.
You can configure any webserver to use the above file for processing requests. You can also run a build-in PHP web server using the following command:
php -S 0.0.0.0:8080 webhook_endpoint.php
This command starts a web server on the port 8080. Requests will be processed by the webhook_endpoint.php
script.
Note: Keep in mind that your server needs to be accessible from the Internet. You cannot provide “localhost”, “127.0.0.1”, or any local address as a webhook endpoint address.