Docs

CMS Integration Guide

Integrate Verimago signing into your newsroom's publish pipeline. Every video your team publishes gets a cryptographic certificate automatically.

Architecture

Editor uploads video → CMS stores file → Webhook computes SHA-384 hash

→ (subscription or freelance one-time for that hash) → POST /v1/sign with hash + metadata → Store verifyUrl → Render shield in article

Your video file never leaves your infrastructure. Only the SHA-384 hash (96 hex) is sent to Verimago. POST /v1/sign succeeds only when the API key’s account has an active publisher/creator subscription or a paid, unused freelance purchase for the same content hash (POST /v1/stripe/checkout-onetime + poll GET /v1/stripe/freelance-purchase — see integration runbook).

Step 1: Get your API key

See Publisher Quick Start — Step 1. You need a persistent API key (full secret starts with vmgo_).

Step 2: Hash the video on your server

Node.js

const crypto = require('crypto');

const fs = require('fs');

function hashFile(filePath) {

return new Promise((resolve, reject) => {

const hash = crypto.createHash('sha384');

const stream = fs.createReadStream(filePath);

stream.on('data', (chunk) => hash.update(chunk));

stream.on('end', () => resolve(hash.digest('hex')));

stream.on('error', reject);

});

}

Python

import hashlib

def hash_file(path):

sha = hashlib.sha384()

with open(path, 'rb') as f:

for chunk in iter(lambda: f.read(8192), b''):

sha.update(chunk)

return sha.hexdigest()

Shell

shasum -a 384 video.mp4 | cut -d' ' -f1   # 96 hex chars

Step 3: Sign via API

const response = await fetch('https://api.verimago.io/v1/sign', {

method: 'POST',

headers: {

'Authorization': Bearer ${VERIMAGO_API_KEY},

'Content-Type': 'application/json',

},

body: JSON.stringify({

contentHash: hash, // 96-char SHA-384 hex

headline: article.title, // from your CMS

journalist: article.author, // from your CMS

location: article.dateline, // optional

recordedAt: video.capturedAt, // optional

contentType: 'AUTHENTIC', // or AI_ENHANCED, AI_GENERATED

captureMode: 'VIDEO', // or PHOTO

}),

});

if (!response.ok) {

if (response.status === 402) {

// Start Stripe freelance checkout or subscription — see runbook §6

throw new Error('Signing requires an active plan or freelance purchase for this hash');

}

throw new Error(await response.text());

}

const { verifyUrl, shieldState, signedAt } = await response.json();

Step 4: Store the certificate reference

Add these fields to your video/article model:

ALTER TABLE videos ADD COLUMN verimago_verify_url TEXT;

ALTER TABLE videos ADD COLUMN verimago_shield TEXT;

ALTER TABLE videos ADD COLUMN verimago_signed_at TIMESTAMPTZ;

Store the verifyUrl, shieldState, and signedAt returned from the sign endpoint.

Step 5: Render the shield

In your article template, render the verification badge:

<div class="verimago-badge">

<a href="{{ video.verimago_verify_url }}" target="_blank" rel="noopener">

<img src="https://verimago.io/badge/{{ video.verimago_shield | lower }}.svg"

alt="Verified by Verimago" width="120" />

</a>

</div>

Platform-specific examples

WordPress

Add to your theme's functions.php or as a plugin:

add_action('add_attachment', function($post_id) {

$file = get_attached_file($post_id);

$mime = get_post_mime_type($post_id);

if (strpos($mime, 'video/') !== 0) return;

$hash = hash_file('sha384', $file);

$response = wp_remote_post('https://api.verimago.io/v1/sign', [

'headers' => [

'Authorization' => 'Bearer ' . VERIMAGO_API_KEY,

'Content-Type' => 'application/json',

],

'body' => json_encode([

'contentHash' => $hash,

'headline' => get_the_title($post_id),

'contentType' => 'AUTHENTIC',

'captureMode' => 'VIDEO',

]),

]);

if (!is_wp_error($response)) {

$body = json_decode(wp_remote_retrieve_body($response), true);

update_post_meta($post_id, '_verimago_verify_url', $body['verifyUrl']);

update_post_meta($post_id, '_verimago_shield', $body['shieldState']);

}

});

Arc XP / Custom CMS

Add a post-publish webhook:

// webhook handler: POST /hooks/verimago-sign

app.post('/hooks/verimago-sign', async (req, res) => {

const { videoUrl, articleId, title, author } = req.body;

// Download video and compute hash

const hash = await hashFromUrl(videoUrl);

// Sign with Verimago

const cert = await signWithVerimago(hash, { headline: title, journalist: author });

// Update article record

await db.articles.update(articleId, {

verimagoVerifyUrl: cert.verifyUrl,

verimagoShield: cert.shieldState,

});

res.json({ ok: true });

});

Batch signing

For archives or bulk uploads, use a script:

#!/bin/bash

API_KEY="vmgo_your_key_here"

for file in videos/*.mp4; do

HASH="$(shasum -a 384 "$file" | cut -d' ' -f1)"

TITLE=$(basename "$file" .mp4)

curl -s -X POST https://api.verimago.io/v1/sign \

-H "Authorization: Bearer $API_KEY" \

-H "Content-Type: application/json" \

-d "{\"contentHash\": \"$HASH\", \"headline\": \"$TITLE\", \"contentType\": \"AUTHENTIC\", \"captureMode\": \"VIDEO\"}" \

| jq '.verifyUrl'

sleep 0.5 # respect rate limits

done

Error handling

ErrorCauseFix
401 UnauthorizedInvalid or expired API keyRegenerate key at Settings → API Keys
400 invalid contentHashWrong length or not hexUse 96-char SHA-384 hex (see POST /v1/sign docs)
400 "contentType must be..."Invalid content typeUse AUTHENTIC, AI_ENHANCED, or AI_GENERATED
409 Conflict (hash already signed)This file was already signedThe response includes the existing certificate details
429 Too Many RequestsRate limit exceededImplement exponential backoff or reduce batch rate

Testing

Use the staging environment for development:

Base URL: https://staging-api.verimago.io

Staging uses software keys (not HSM) and a separate database. Certificates created on staging are not publicly verifiable.