Webhooks let you receive real-time HTTP notifications when events happen in your Uplup forms and quizzes. Instead of polling the API, webhooks push data to your server the moment something occurs — a submission is received, a quiz is completed, a form is published, and more.
Uplup webhooks follow the same industry-standard patterns used by Stripe, GitHub, and Typeform: HTTPS delivery, HMAC-SHA256 signatures for verification, and structured JSON payloads.
Webhooks are available on Business and Scale plans only.
| Plan | Webhooks | Events |
|---|---|---|
| Free | — | Not available |
| Starter | — | Not available |
| Pro | — | Not available |
| Business | 10 webhooks | All 19 events |
| Scale | 100 webhooks | All 19 events |
You can manage webhooks in two ways:
/api/v1/webhooks endpoints with a Bearer tokenImportant: When you create a webhook, a signing secret is displayed once. Copy and save it securely — you’ll need it to verify webhook signatures. It cannot be retrieved again.
Uplup supports 19 webhook events organized into 5 categories. Each webhook subscribes to one event type.
| Event | Description |
|---|---|
form.created |
A new form or quiz was created |
form.updated |
Form settings were modified |
form.deleted |
A form was deleted |
form.published |
A form was published (made live) |
form.unpublished |
A form was taken offline |
form.cloned |
A form was duplicated |
| Event | Description |
|---|---|
submission.created |
A new form response was received |
submission.completed |
A multi-step submission was completed (reserved) |
submission.deleted |
A submission was deleted |
| Event | Description |
|---|---|
quiz.completed |
A quiz was scored and results generated |
quiz.passed |
A quiz taker met the passing score threshold |
quiz.failed |
A quiz taker did not meet the passing score |
quiz.timer_expired |
A quiz timer ran out before the taker could finish |
| Event | Description |
|---|---|
field.created |
A field was added to a form |
field.updated |
A field’s properties were modified |
field.deleted |
A field was removed from a form |
| Event | Description |
|---|---|
response.started |
A user began filling out a form (first interaction per session) |
response.page_completed |
A user completed a form page (with field data for that page) |
response.abandoned |
A user left the form without completing it |
Every webhook delivery sends a JSON payload with this structure:
{
"event": "submission.created",
"webhook_id": 42,
"form_id": "abc12345",
"timestamp": "2026-03-09T12:00:00Z",
"data": {
"submission_id": "sub_789",
"form_id": "abc12345",
"fields": {
"email": "user@example.com",
"name": "Jane Smith"
}
}
}
Each webhook request includes these headers:
| Header | Description |
|---|---|
Content-Type |
application/json |
X-Uplup-Signature |
HMAC-SHA256 hex digest of the request body |
X-Webhook-Event |
The event type (e.g., submission.created) |
X-Webhook-ID |
Unique ID of the webhook subscription |
User-Agent |
Uplup-Webhooks/1.0 |
Always verify the X-Uplup-Signature header to confirm the request came from Uplup. The signature is an HMAC-SHA256 hex digest of the raw request body, using your webhook’s signing secret as the key.
const crypto = require('crypto');
function verifyWebhook(body, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(body, 'utf8')
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Express.js example
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-uplup-signature'];
if (!verifyWebhook(req.body, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
console.log('Received:', event.event);
res.status(200).send('OK');
});
import hmac
import hashlib
def verify_webhook(body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode('utf-8'),
body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
# Flask example
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-Uplup-Signature', '')
if not verify_webhook(request.data, signature, WEBHOOK_SECRET):
return 'Invalid signature', 401
event = request.get_json()
print(f"Received: {event['event']}")
return 'OK', 200
<?php
$body = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_UPLUP_SIGNATURE'] ?? '';
$secret = getenv('WEBHOOK_SECRET');
$expected = hash_hmac('sha256', $body, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit('Invalid signature');
}
$event = json_decode($body, true);
error_log('Received: ' . $event['event']);
http_response_code(200);
echo 'OK';
You can test your webhook endpoint directly from the dashboard:
Test events are rate-limited to 5 per minute per webhook. Test deliveries appear in the delivery log just like real events.
Toggle the switch next to any webhook to pause or resume it. Paused webhooks do not receive any events.
Click the delete button and confirm to remove a webhook. Deleted webhooks stop receiving events immediately.
When creating a webhook, you can optionally select a specific form. The webhook will only fire for events related to that form. Leave the filter empty to receive events for all forms.
X-Uplup-Signature headerwebhook_id and timestamp to detect duplicate deliveriesWebhooks are available on Business (10 webhooks) and Scale (100 webhooks) plans. Free, Starter, and Pro plans do not include webhook access.
Each webhook subscribes to one event type. To receive multiple event types, create separate webhooks — one per event. This gives you fine-grained control over which events go to which endpoints.
No. Failed deliveries are logged with error details, but not automatically retried. You can re-test manually using the Send Test button, or check delivery logs to diagnose issues.
Yes. When creating a webhook, you can select a specific form to filter events. Only events from that form will trigger the webhook. Leave the filter empty to receive events for all forms in your workspace.
The delivery will fail and be logged with the error. Uplup does not retry failed deliveries. Monitor your delivery logs regularly to catch any issues.