HomeDocumentation

Sparker Verify Documentation

Learn how to integrate hardware-backed media verification into your application. From quick setup to browser capture and public verification.

API Reference

Overview

Sparker Verify prevents AI-generated, edited, or re-uploaded media from being passed off as authentic captures. It binds every image, video, or audio file to a hardware-backed private key on the user's physical device at the moment of capture, then wraps the cryptographic proof in a C2PA manifest that any relying party can verify independently.

Hardware-backed signing

ECDSA P-256 keys generated inside secure hardware. Today the SDK supports browser WebAuthn; native iOS and Android support is planned.

Nonce-challenge protocol

Every verification uses a server-generated nonce with 5-minute TTL to prevent replay attacks.

Public verification

Anyone can verify media authenticity using the public API or SDK. Files never leave the client.

Entities

EntityRole
Prover (Client Device)Holds hardware-backed private key, signs file+nonce hash
Verifier (Sparker Backend)Orchestrates challenges, validates signatures, mints C2PA manifests
Relying Party (Business)Consumes verified media and checks authenticity via public API

Authentication

Sparker uses Google OAuth for dashboard access and dashboard-managed API keys for SDK traffic. Initialize the SDK once with an API key and it will bind verification requests to that account. Public verification endpoints still work without authentication, but they are much more heavily rate-limited.

For delegated third-party capture, 3P verification link tokens let you share verification capability with people who don't have Sparker accounts. See the for details.

Quickstart

The SDK now has two fast paths: a verification overlay for published media and a capture component for live browser submissions.

1. Install the SDK

bash
npm install @sparker-io/verify-sdk

2. Wrap a published image

typescript
import { SparkerVerify } from '@sparker-io/verify-sdk'
import { SparkerVerifiedImage } from '@sparker-io/verify-sdk/react'

const client = new SparkerVerify({
  apiUrl: 'https://verify.sparker.io',
  appUrl: 'https://verify.sparker.io',
  apiKey: 'spk_live_...',
})

export function StoryImage() {
  return (
    <SparkerVerifiedImage client={client}>
      <img
        src="/story/harbor-wall.jpg"
        alt="Concrete crews reinforcing the harbor wall"
      />
    </SparkerVerifiedImage>
  )
}

3. Drop in the capture desk

The React capture component handles first-run browser registration, nonce creation, signature generation, and upload.

typescript
import { SparkerCapture } from '@sparker-io/verify-sdk/react'

export function CaptureDesk() {
  return (
    <SparkerCapture
      apiKey="spk_live_..."
      apiUrl="https://verify.sparker.io"
      appUrl="https://verify.sparker.io"
      onVerified={(result) => {
        console.log(result.id)
        console.log(result.signature_hash)
        console.log(result.verificationUrl)
      }}
    />
  )
}

4. Use the low-level client when needed

typescript
const sdk = new SparkerVerify({
  apiUrl: 'https://verify.sparker.io',
  appUrl: 'https://verify.sparker.io',
  apiKey: 'spk_live_...',
})

await sdk.registerBrowserDevice('Field Reporter Browser')

const verification = await sdk.submitCapturedFile(file, {
  metadata: {
    gps: {
      latitude: 40.7128,
      longitude: -74.006,
      accuracy: 8,
    },
  },
})

console.log(verification.signature_hash)
console.log(
  sdk.buildVerificationUrl(
    verification.signature_hash,
    undefined,
    verification.id,
  ),
)
Next steps
  • Read the concept to understand what happens under the hood
  • Explore the API reference for all available endpoints
  • Check the Next.js demo in sdk/examples/nextjs-demo/ for a newsroom-style integration example

Concepts

Understanding these core concepts will help you integrate Sparker Verify effectively and make informed architectural decisions.

C2PA standard

The Coalition for Content Provenance and Authenticity (C2PA) is an open technical standard that provides a way to trace the origin and history of digital content. Sparker Verify uses C2PA manifests to embed cryptographic provenance data directly into media files.

A C2PA manifest contains claims and assertions. Sparker creates a custom assertion called Hardware Verified Capture that records:

  • The hardware attestation proof (device platform, attestation format)
  • The nonce used during capture (single-use, 5-minute TTL)
  • The device signature verification status (verified against stored public key)
  • GPS coordinates at time of capture (if provided)
  • Device information (name, model, platform)

The manifest is signed with the platform's X.509 certificate chain, making it independently verifiable by any C2PA-compatible tool.

C2PA compatibility
Sparker manifests are compatible with C2PA v1.3+. Any tool that reads C2PA metadata (Adobe Content Credentials, Truepic, etc.) can verify Sparker-signed media.

Hardware attestation

Hardware attestation proves that a cryptographic key was generated and is stored inside a Trusted Execution Environment (TEE). This provides a strong guarantee that the signing key cannot be extracted, copied, or used by software alone.

PlatformStatusAttestation formatNotes
WebSupported nowwebauthnBrowser capture and device registration through WebAuthn
iOSPlannedNot supported yetNative iOS SDK and Secure Enclave flow are not available yet
AndroidPlannedNot supported yetNative Android SDK and StrongBox / TEE flow are not available yet

Supported device registrations currently use the browser WebAuthn flow and submit public keys in a unified format: {algorithm: "ES256", x: "<base64>", y: "<base64>"}. Native iOS and Android formats will follow the same server-side model once those SDKs ship.

Key security
Hardware-backed keys cannot be exported from the device. If a device is lost or factory-reset, the key pair is destroyed permanently. Users should register multiple devices for redundancy.

Verification flow

The verification lifecycle has three phases: device registration, media capture with signing, and public verification.

Phase 1: Device registration (one-time)

Client                                  Server
  |                                       |
  |--- POST /devices/challenge ---------->|
  |<-- { challenge } ---------------------|
  |                                       |
  |  [generate key pair in TEE]           |
  |  [sign challenge with private key]    |
  |  [collect attestation proof]          |
  |                                       |
  |--- POST /devices/register ----------->|
  |    { platform, device_id,             |
  |      public_key, attestation }        |
  |                                       |
  |    [verify challenge_response]        |
  |    [verify attestation chain]         |
  |    [store public_key + device_id]     |
  |                                       |
  |<-- { id, device_id, platform } -------|

Phase 2: Capture and sign

Client                                  Server
  |                                       |
  |--- POST /verifications -------------->|
  |<-- { id, nonce, expires_at } ---------|
  |                                       |
  |  [capture media live]                 |
  |  [compute SHA-256(file + nonce)]      |
  |  [sign hash with hardware key]        |
  |                                       |
  |--- POST /verifications/verify ------->|
  |    { id, file, device_id,             |
  |      signature, metadata }            |
  |                                       |
  |    [verify nonce is valid + unused]   |
  |    [lookup device by device_id]       |
  |    [verify signature with public_key] |
  |    [create C2PA manifest]             |
  |    [store file + register hash]       |
  |                                       |
  |<-- { authentic,                       |
  |      signature_hash } ---------------|

Phase 3: Public verification

Relying parties verify media authenticity without uploading files. The signature hash (64-character hex string extracted from the C2PA manifest) is checked against Sparker's registry.

typescript
// Client-side verification (no file upload)
const result = await sdk.verifySignature('abc123def456...')
if (result.authentic) {
  console.log('Verified at:', result.verified_at)
}

Guides

Web integration

Integrate Sparker Verify into your web application using the JavaScript SDK. This guide covers the complete flow from setup to verification.

Prerequisites

  • A Sparker account with API access
  • A modern browser with WebAuthn support (Chrome 67+, Firefox 60+, Safari 14+)
  • HTTPS enabled on your domain (required for WebAuthn)

Authentication setup

Generate an API key in the dashboard, then initialize the SDK with that key. OAuth still powers dashboard sessions, but the SDK itself should use the API key.

typescript
const sdk = new SparkerVerify({
  apiUrl: 'https://verify.sparker.io',
  appUrl: 'https://verify.sparker.io',
  apiKey: 'spk_live_...',
})

Checking WebAuthn support

typescript
import { isWebAuthnAvailable } from '@sparker-io/verify-sdk'

if (!isWebAuthnAvailable()) {
  // Show fallback: WebAuthn not supported
  showError('Your browser does not support hardware-backed signing.')
}

Error handling

The SDK throws typed errors you can catch and handle appropriately.

typescript
import { SparkerError } from '@sparker-io/verify-sdk'

try {
  const result = await sdk.submitCapturedFile(file)
} catch (error) {
  if (error instanceof SparkerError && error.status === 429) {
    // Rate limited - retry after delay
    showRateLimitWarning(60)
  } else if (error instanceof SparkerError && error.status === 400) {
    // Bad request - check input
    showError(error.message)
  } else {
    showError('Verification failed. Please try again.')
  }
}

Using the SDK

The SDK is intentionally component-first. Use the React layer when you want fast adoption, then drop down to the low-level client for custom UI or non-React flows.

Current platform support
The production SDK currently supports browser capture and device registration only. Native iOS and Android SDKs are not supported yet.

Verification overlay

Wrap an existing image, let the SDK fetch the bytes, resolve the lookup hash, and show the compact green verification badge in the corner. When the backend returns a matched verification ID, the badge links directly to the frontend verification page for that record.

typescript
import { SparkerVerify } from '@sparker-io/verify-sdk'
import { SparkerVerifiedImage } from '@sparker-io/verify-sdk/react'

const client = new SparkerVerify({
  apiUrl: 'https://verify.sparker.io',
  appUrl: 'https://verify.sparker.io',
  apiKey: 'spk_live_...',
})

<SparkerVerifiedImage client={client}>
  <img src={imageUrl} alt="Field photograph" />
</SparkerVerifiedImage>

Capture workflow

SparkerCapture handles registration, the nonce request, the browser signature step, and the upload to Sparker.

typescript
import { SparkerCapture } from '@sparker-io/verify-sdk/react'

<SparkerCapture
  apiKey="spk_live_..."
  apiUrl="https://verify.sparker.io"
  appUrl="https://verify.sparker.io"
/>

Low-level verification

typescript
const sdk = new SparkerVerify({
  apiUrl: 'https://verify.sparker.io',
  appUrl: 'https://verify.sparker.io',
  apiKey: 'spk_live_...',
})

// Option 1: Verify by lookup hash
const lookupHash = 'abc123...'
const result = await sdk.verifySignature(lookupHash)

// Option 2: Verify a local file
const local = await sdk.verifyFile(file)

// Option 3: Verify a published image URL
const remote = await sdk.verifySource(imageUrl)

// Build the public verification URL
const url = sdk.buildVerificationUrl(
  lookupHash,
  imageUrl,
  result.verification_id,
)
Privacy by design
Verification lookups stay lightweight. The SDK prefers the embedded C2PA signature hash when it can read one, and falls back to the final file hash when that is the active public lookup path.
Current auth model
SDK flows should use dashboard-generated API keys. For delegated capture, pass a 3P verification link token instead of an account key.

3P verification links

3P verification links let authenticated users delegate capture to third parties who do not have Sparker accounts. Common use cases include insurance claims, journalism workflows, and field documentation.

Creating a 3P verification link

typescript
// Create a token with 10 uses, expiring in 7 days
const token = await sdk.createToken({
  max_uses: 10,
  expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
})

// Share the 3P verification link
console.log(token.url)
// https://verify.sparker.io/capture?token=...

How 3P verification links work

  1. User opens the 3P verification link. No login or account creation required.
  2. The token is validated against the server. Invalid or expired tokens show an error.
  3. User captures media directly through the browser camera.
  4. The verification is associated with the token creator's account and quota.
  5. The token's usage count increments. When max uses are reached, the token expires.

Managing tokens

typescript
// List all your tokens
const { tokens } = await sdk.listTokens()

// Revoke a token
await sdk.revokeToken(tokenId)

// Check token validity (public, no auth required)
const status = await sdk.validateToken('token-string')
console.log(status.valid, status.remaining_uses)
Quota usage
Verifications created via 3P verification link tokens count against the token creator's quota, not the third-party participant's. Monitor your quota in the dashboard to avoid exceeding limits.

SDK reference

Complete reference for the core client and the React component layer.

Constructor

typescript
new SparkerVerify(config: {
  apiUrl: string      // Sparker API base URL
  appUrl?: string     // Public frontend base URL for verification links
  apiKey?: string     // Dashboard-generated API key for SDK traffic
  timeout?: number    // Request timeout in ms (default: 30000)
})

Authentication

MethodDescription
setApiKey(key)Set or rotate the API key used for SDK requests
setAnonymousToken(token)Use a 3P verification link token instead of the account key (legacy helper name)
clearApiKey()Remove the configured API key

React exports

MethodDescription
SparkerVerifiedImageWrap an image and render the hover-to-expand badge
SparkerCaptureBrowser capture flow with registration, signing, and upload
@sparker-io/verify-sdk/reactReact-only entry point for the component layer

Browser capture helpers

MethodDescription
registerBrowserDevice(name?)Create and persist the browser WebAuthn credential
getStoredBrowserDevice()Return the stored browser credential metadata
clearStoredBrowserDevice()Forget the stored browser credential reference
submitCapturedFile(file, options)Create nonce, sign, and upload a captured file in one call

Verification methods (auth optional)

MethodDescription
createVerification()Create verification and receive a 5-minute nonce
submitVerification(...)Upload signed media with device_id, signature, and metadata
getVerification(id)Get verification status and details

Public verification methods (no auth)

MethodDescription
verifySignature(hash)Check signature hash against registry
verifyFile(file)Resolve a local lookup hash, then verify
verifySource(url)Fetch a published asset and verify it client-side
verifyBatch(hashes)Verify up to 100 hashes at once
getVerificationDetails(id)Get public verification details for a public verification record
buildVerificationUrl(hash, src?, verificationId?)Build a public verification URL, preferring `/verify/:id` when a matched verification ID is available

Platform helpers

FunctionDescription
computeSignedData(file, nonce)Compute SHA-256(file + nonce) for signing
extractWebAuthnPublicKey(response)Extract P-256 x,y from WebAuthn attestation
buildWebAuthnCreateOptions(challenge, userId, name)Build WebAuthn registration options
buildWebAuthnGetOptions(hash, credentialId)Build WebAuthn assertion options
isWebAuthnAvailable()Check if WebAuthn is supported
bufferToBase64Url(buffer)ArrayBuffer to base64url string
base64ToBuffer(base64)Base64url string to ArrayBuffer
sha256Hex(data)SHA-256 hash as hex string

Types

typescript
type VerificationLookupStrategy = "c2pa_signature" | "file_hash"

interface ResolvedPublicVerifyResponse {
  authentic: boolean
  verified_at: string
  lookup_hash: string
  lookup_strategy: VerificationLookupStrategy
}

interface VerificationVerifyResponse {
  id: string
  authentic: boolean
  signature_hash: string
  verified_at: string
  device: {
    name: string
    model: string
    platform: string
  } | null
  gps: GPSData | null
}

interface TokenValidateResponse {
  valid: boolean
  creator_name: string | null
  remaining_uses: number | null
  expires_at: string | null
}

Platforms

Sparker Verify currently supports hardware-backed signing in web browsers through WebAuthn. Native iOS and Android SDKs are planned, but they are not supported yet.

Web (WebAuthn)

Web uses the WebAuthn/FIDO2 API to access platform authenticators like TouchID and Windows Hello. Keys are ECDSA P-256, generated in the browser's platform authenticator.

typescript
import {
  buildWebAuthnCreateOptions,
  extractWebAuthnPublicKey,
  bufferToBase64Url,
} from '@sparker-io/verify-sdk'

// Registration
const options = buildWebAuthnCreateOptions(challenge, userId, userEmail)
const credential = await navigator.credentials.create({ publicKey: options })
const response = credential.response as AuthenticatorAttestationResponse
const publicKey = extractWebAuthnPublicKey(response)

// Signing
const assertion = await navigator.credentials.get({
  publicKey: buildWebAuthnGetOptions(signedDataHash, credentialId),
})
const signature = bufferToBase64Url(
  (assertion.response as AuthenticatorAssertionResponse).signature
)
Browser requirements
WebAuthn requires HTTPS (except on localhost). Supported: Chrome 67+, Firefox 60+, Safari 14+, Edge 79+. Use isWebAuthnAvailable() to check support before attempting registration.

iOS (planned)

Native iOS device registration and capture are not supported yet. A future iOS SDK is expected to use Secure Enclave-backed keys, but there is no supported production flow today.

Availability
Use the web/browser SDK today. If you are planning a native iOS integration, treat it as roadmap work rather than a currently supported platform.

Android (planned)

Native Android device registration and capture are not supported yet. A future Android SDK is expected to target StrongBox or TEE-backed keys, but there is no supported production flow today.

Availability
Use the web/browser SDK today. If you are planning a native Android integration, treat it as roadmap work rather than a currently supported platform.