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

  1. Open Settings:
    • Press F1 or click the settings button to display the Settings dialog.
  1. Select Upload Tab:
    • Go to the Upload tab.
  1. Choose Custom Upload:
    • Select "Custom Upload" from the list of uploaders.
  1. Enter URL:
    • Enter the URL that Sparkbooth should POST to on your server.
  1. 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;
        }
    }
}

Still need help? Contact Us Contact Us