Custom Uploads to Your Server
Note: This feature requires you to implement server-side code on your server. We do not provide support for this.
Sparkbooth can upload photos to a custom server using the "Custom Upload" option. This requires you to implement server-side code (NodeJS, PHP, Ruby on Rails, ASP.net, etc.) on your server to receive the POST request.
Steps to Enable Custom Upload
- Open Settings:
- Press F1 or click the settings button to display the Settings dialog.
- Select Upload Tab:
- Go to the Upload tab.
- Choose Custom Upload:
- Select "Custom Upload" from the list of uploaders.
- Enter URL:
- Enter the URL that Sparkbooth should POST to on your server.
- Provide Credentials and Message:
-
Optionally, provide a username, password, and message.

Data Posted to the Server
Sparkbooth will POST the following multipart/form-data data to the URL:
- media: Binary image data. If base64, it contains base64 image data.
- username: String username from custom upload settings.
- password: String password from custom upload settings.
- name: String from name prompt.
- email: String from email prompt.
- message: String from custom upload settings or from the name/email prompt.
Server Response
If you selected the JSON server response, the expected success response from the server should look like:
{
"status": true,
"error": "error message if not successful",
"url": "optional URL link to the photo used for QR code feature"
}
If you selected the XML server response, the expected success response from the server should look like:
<?xml version="1.0" encoding="UTF-8"?> <rsp status="ok" url="optional URL link to the photo used for QR code feature" />
The failed response should look like:
<?xml version="1.0" encoding="UTF-8"?> <rsp status="fail"> <err msg="Your error message here" /> </rsp>
Example Server-Side Code
Node.js Example:
// Sparkbooth Custom Upload Receiver
//
// Saves uploads to ./upload/<client>/ and replies with JSON.
//
// In Sparkbooth's Custom Upload settings, set the response format to JSON
//
// Setup: npm install express multer
// Run: node upload.js
// Point Sparkbooth Service URL to:
// https://yourdomain.com/upload?client=myclient&username=...&password=...
const express = require('express');
const multer = require('multer');
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
// TODO: replace these placeholders with values loaded from a secure secret
// store (env var, secrets manager, etc.). Do NOT commit real credentials.
const EXPECTED_USERNAME = process.env.SPARKBOOTH_USERNAME || 'CHANGE_ME';
const EXPECTED_PASSWORD = process.env.SPARKBOOTH_PASSWORD || 'CHANGE_ME';
const ROOT = path.join(__dirname, 'upload');
const upload = multer({ storage: multer.memoryStorage() });
const app = express();
app.post('/upload', upload.single('media'), (req, res) => {
const username = req.query.username || req.body.username || '';
const password = req.query.password || req.body.password || '';
if (!safeEqual(username, EXPECTED_USERNAME) ||
!safeEqual(password, EXPECTED_PASSWORD)) {
return respondFail(res, 'Unauthorized.');
}
if (!req.file) return respondFail(res, 'No file uploaded.');
const client = (req.query.client || req.body.client || '').replace(/[^A-Za-z0-9_-]/g, '');
const dir = client ? path.join(ROOT, client) : ROOT;
fs.mkdirSync(dir, { recursive: true });
const target = uniquePath(dir, path.basename(req.file.originalname));
fs.writeFileSync(target, req.file.buffer);
res.json({ status: 'ok' });
});
function respondFail(res, msg) {
res.json({ status: 'fail', err: { msg: String(msg) } });
}
function uniquePath(dir, name) {
const target = path.join(dir, name);
if (!fs.existsSync(target)) return target;
const ext = path.extname(name);
const stem = path.basename(name, ext);
for (let i = 1; i < 10000; i++) {
const candidate = path.join(dir, `${stem}(${i})${ext}`);
if (!fs.existsSync(candidate)) return candidate;
}
throw new Error('Too many files by this name.');
}
function safeEqual(a, b) {
const ab = Buffer.from(a);
const bb = Buffer.from(b);
if (ab.length !== bb.length) return false;
return crypto.timingSafeEqual(ab, bb);
}
const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`Sparkbooth upload receiver listening on :${port}`));
PHP Example:
<?php
/*
* Sparkbooth Custom Upload Receiver
*
* Saves uploads to /upload/<client>/ and replies with the XML
*
* Usage: point Sparkbooth Service URL to:
* https://yourdomain.com/upload.php?client=myclient&username=...&password=...
*/
// TODO: replace these placeholders with values loaded from a secure secret
// store (env var, secrets manager, etc.). Do NOT commit real credentials.
$expectedUsername = 'CHANGE_ME';
$expectedPassword = 'CHANGE_ME';
$username = $_REQUEST['username'] ?? '';
$password = $_REQUEST['password'] ?? '';
if (!hash_equals($expectedUsername, $username) || !hash_equals($expectedPassword, $password)) {
respondFail("Unauthorized.");
}
$root = __DIR__ . '/upload';
$client = preg_replace('/[^A-Za-z0-9_-]/', '', $_REQUEST['client'] ?? '');
$targetDir = $client === '' ? $root : "$root/$client";
if (!is_dir($targetDir) && !mkdir($targetDir, 0755, true)) {
respondFail("Could not create upload directory.");
}
if (!isset($_FILES['media']) || $_FILES['media']['error'] !== UPLOAD_ERR_OK) {
respondFail("No file uploaded.");
}
$target = uniquePath($targetDir, basename($_FILES['media']['name']));
if (!move_uploaded_file($_FILES['media']['tmp_name'], $target)) {
respondFail("Could not save uploaded file.");
}
echo '<?xml version="1.0" encoding="UTF-8"?><rsp status="ok" />';
function uniquePath($dir, $name) {
$path = "$dir/$name";
if (!file_exists($path)) return $path;
$info = pathinfo($name);
$base = $info['filename'];
$ext = isset($info['extension']) ? '.' . $info['extension'] : '';
$i = 1;
while (file_exists("$dir/$base($i)$ext")) $i++;
return "$dir/$base($i)$ext";
}
function respondFail($msg) {
$safe = htmlspecialchars($msg, ENT_QUOTES, 'UTF-8');
echo '<?xml version="1.0" encoding="UTF-8"?><rsp status="fail"><err msg="' . $safe . '" /></rsp>';
exit;
}
?>
ASP.net example
using System;
using System.IO;
using System.Text.RegularExpressions;
using System.Web.Services;
namespace WebApplication1
{
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
public class WebService1 : WebService
{
// TODO: replace these placeholders with values loaded from a secure
// secret store (encrypted web.config section, Azure Key Vault, env
// var, etc.). Do NOT commit real credentials.
private const string ExpectedUsername = "CHANGE_ME";
private const string ExpectedPassword = "CHANGE_ME";
private const string RootPath = @"C:\Temp\sparkbooth";
[WebMethod]
public void Upload()
{
try
{
var username = Context.Request["username"] ?? "";
var password = Context.Request["password"] ?? "";
if (!FixedTimeEquals(username, ExpectedUsername) ||
!FixedTimeEquals(password, ExpectedPassword))
{
RespondFail("Unauthorized.");
return;
}
var media = Context.Request.Files["media"];
if (media == null || media.ContentLength == 0)
{
RespondFail("No file uploaded.");
return;
}
var client = Regex.Replace(Context.Request["client"] ?? "", "[^A-Za-z0-9_-]", "");
var targetDir = string.IsNullOrEmpty(client) ? RootPath : Path.Combine(RootPath, client);
Directory.CreateDirectory(targetDir);
var safeName = Path.GetFileName(media.FileName);
var targetPath = UniquePath(targetDir, safeName);
using (var output = File.Create(targetPath))
{
media.InputStream.CopyTo(output);
}
Context.Response.Write("<?xml version=\"1.0\" encoding=\"UTF-8\"?><rsp status=\"ok\" />");
}
catch (Exception ex)
{
RespondFail(ex.Message);
}
}
private void RespondFail(string message)
{
var safe = System.Security.SecurityElement.Escape(message) ?? "";
Context.Response.Write(
$"<?xml version=\"1.0\" encoding=\"UTF-8\"?><rsp status=\"fail\"><err msg=\"{safe}\" /></rsp>");
}
private static string UniquePath(string dir, string name)
{
var path = Path.Combine(dir, name);
if (!File.Exists(path)) return path;
var stem = Path.GetFileNameWithoutExtension(name);
var ext = Path.GetExtension(name);
for (var i = 1; i < 10000; i++)
{
var candidate = Path.Combine(dir, $"{stem}({i}){ext}");
if (!File.Exists(candidate)) return candidate;
}
throw new Exception("Too many files by this name.");
}
private static bool FixedTimeEquals(string a, string b)
{
if (a.Length != b.Length) return false;
var diff = 0;
for (var i = 0; i < a.Length; i++) diff |= a[i] ^ b[i];
return diff == 0;
}
}
}
