Photo Collect API
Photo Collect ↗

Signed upload links without an API call

A Deeplink carries invitation data in a Base64-encoded UTF-8 JSON payload. Opening the link creates a persistent invitation only when the link is used, which makes Deeplinks useful for portal buttons, QR codes, printed letters, and iframes.

Each signed payload supports one upload. Use a new, cryptographically random salt for every new link, including later uploads by the same customer.

URL format

https://demo.photocollect.io/collect/new?payload=<percent-encoded-base64-json>&sig=<hex-hmac>[&locale=en_US]
Query parameter Requirement Description
payload Required Standard Base64 without line breaks, percent-encoded as a query value. Decode it to obtain the UTF-8 JSON bytes.
sig Required Lowercase hexadecimal HMAC-SHA256 of the exact UTF-8 JSON bytes.
locale Optional Upload interface locale such as en_US or de_DE.

Payload fields

Field Requirement Description
site_code Required Site receiving the upload.
salt Required Unique value for this link. At least 128 bits of randomness is recommended.
customer_no Optional Your business identifier. Photo Collect creates one when omitted.
customer_to Optional Email address or phone number for follow-up communication.
expiry_date Required Link expiry in YYYY-MM-DD format, end of the calendar day (23:59:59) of the default setup timezone or expiry_timezone.
expiry_timezone Optional IANA timezone such as Europe/Berlin.
config Optional The same invitation overrides accepted by POST /invitation, including collect_redirect_uri and its supported placeholders.
{
  "customer_no": "12345678",
  "site_code": "demo",
  "customer_to": "user@example.com",
  "expiry_date": "2026-12-31",
  "expiry_timezone": "Europe/Berlin",
  "salt": "1712928000",
  "config": {
    "collect_redirect_uri": "https://example.com/complete?customer=[CUSTOMER_NO]&site=[SITE_CODE]"
  }
}

Sign the exact UTF-8 JSON bytes before Base64 encoding them. Whitespace, escaping, and key order all affect the signature. Percent-encode the resulting Base64 when adding it to the URL; do not use Base64URL unless your setup explicitly supports it.

Keep the Deeplink secret in a trusted server-side environment. Do not include it in a payload, browser bundle, workbook, URL, or source repository. If a client-side tool needs to create links, have it call an authenticated server-side signing endpoint.

Implementation examples

Choose a language below for an implementation pattern. The server-side examples are copy-ready after you provide the environment variable and setup values. Spreadsheet examples include the extra deployment and secret-handling constraints that apply to their runtimes.

IMPLEMENTATION EXAMPLES

Choose your language

Every example signs directly. For production Excel add-ins, keep the secret behind a trusted signing service.

PHP

Uses native JSON, Base64, HMAC, and RFC 3986 query encoding.

PHP 8+ · no dependencies
<?php

$secret = getenv('PHOTOCOLLECT_DEEPLINK_SECRET');
if ($secret === false || $secret === '') {
    throw new RuntimeException('Missing PHOTOCOLLECT_DEEPLINK_SECRET');
}

$payload = [
    'customer_no' => '12345678',
    'site_code' => 'demo',
    'customer_to' => 'user@example.com',
    'expiry_date' => (new DateTimeImmutable(
        '+90 days',
        new DateTimeZone('UTC')
    ))->format('Y-m-d'),
    'expiry_timezone' => 'UTC',
    'salt' => bin2hex(random_bytes(16)),
    'config' => [
        'collect_redirect_uri' => 'https://example.com/thank-you',
    ],
];

// Sign and send this exact JSON string.
$payloadJson = json_encode(
    $payload,
    JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR
);
$payloadEncoded = base64_encode($payloadJson);
$sig = hash_hmac('sha256', $payloadJson, $secret); // lowercase HEX

$query = http_build_query([
    'payload' => $payloadEncoded,
    'sig' => $sig,
    'locale' => 'en_US',
], '', '&', PHP_QUERY_RFC3986);

$url = 'https://demo.photocollect.io/collect/new?' . $query;
echo $url;
⌁

Connect your API

Credentials are kept in this session only.