Easy2257

Quickstart

Integrate Easy2257 in 15 minutes.

Your platform does not need to know whether a creator already has an Easy2257 account. Call POST /v1/solo-accounts with their email and your internal user ID — the response tells you what to do next.

1. Provision the creator

curl -X POST https://easy2257.com/api/v1/solo-accounts \
  -H "Authorization: Bearer $EZ2257_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $EXTERNAL_USER_ID" \
  -d '{"externalUserId":"user_12345","email":"creator@example.com","callbackUrl":"https://your-site.com/webhooks/ez2257"}'
const res = await fetch('https://easy2257.com/api/v1/solo-accounts', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.EZ2257_API_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': externalUserId,
  },
  body: JSON.stringify({
    externalUserId: 'user_12345',
    email: 'creator@example.com',
    callbackUrl: 'https://your-site.com/webhooks/ez2257',
  }),
});
const { accountId, status, onboardingUrl } = await res.json();
if (status !== 'active') redirect(onboardingUrl);
// status === 'active' → creator already has Easy2257; grant upload now
import os, requests

r = requests.post(
    'https://easy2257.com/api/v1/solo-accounts',
    headers={
        'Authorization': f'Bearer {os.environ["EZ2257_API_KEY"]}',
        'Idempotency-Key': external_user_id,
    },
    json={
        'externalUserId': 'user_12345',
        'email': 'creator@example.com',
        'callbackUrl': 'https://your-site.com/webhooks/ez2257',
    },
)
data = r.json()
if data['status'] != 'active':
    return redirect(data['onboardingUrl'])
# status == 'active' → grant upload now
$ch = curl_init('https://easy2257.com/api/v1/solo-accounts');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('EZ2257_API_KEY'),
        'Content-Type: application/json',
        'Idempotency-Key: ' . $externalUserId,
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'externalUserId' => 'user_12345',
        'email' => 'creator@example.com',
        'callbackUrl' => 'https://your-site.com/webhooks/ez2257',
    ]),
]);
$data = json_decode(curl_exec($ch), true);
if ($data['status'] !== 'active') {
    header('Location: ' . $data['onboardingUrl']); exit;
}
// status === 'active' → grant upload now

The response is one of two things:

  • { status: "active" } — creator already has Easy2257. Grant upload access immediately.
  • { status: "pending_id_verification", onboardingUrl } — redirect the creator to onboardingUrl in top-level navigation (not an iframe — ID verification requires camera access).

The creator completes identity verification and subscribes on Easy2257's domain. Your webhook fires when they're done.

2. Receive the verified webhook

# Verify the signature before trusting the payload
# Header format: X-EZ2257-Signature: t=1714000000,v1=abc123...
# Signed payload: "{t}.{rawBody}"  (HMAC-SHA256 of your webhook secret)
// app/api/webhooks/ez2257/route.ts (Next.js App Router)
import { createHmac, timingSafeEqual } from 'crypto';

function verify(rawBody, header, secret) {
  const p = Object.fromEntries(header.split(',').map(s => s.split('=')));
  const expected = createHmac('sha256', secret)
    .update(`${p.t}.${rawBody}`).digest('hex');
  return timingSafeEqual(Buffer.from(p.v1), Buffer.from(expected));
}

export async function POST(req) {
  const rawBody = await req.text(); // NEVER req.json() — raw bytes only
  const sig = req.headers.get('x-ez2257-signature') ?? '';
  if (!verify(rawBody, sig, process.env.EZ2257_WEBHOOK_SECRET)) {
    return new Response('Unauthorized', { status: 401 });
  }
  const event = JSON.parse(rawBody);
  if (event.type === 'solo_account.verified') {
    await grantUploadAccess(event.data.externalUserId);
  }
  return new Response('ok');
}
import hmac, hashlib, os, json
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = os.environ['EZ2257_WEBHOOK_SECRET']

def verify(raw_body: bytes, header: str) -> bool:
    parts = dict(p.split('=', 1) for p in header.split(',') if '=' in p)
    payload = f"{parts['t']}.".encode() + raw_body
    expected = hmac.new(SECRET.encode(), payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(parts.get('v1', ''), expected)

@app.post('/webhooks/ez2257')
def webhook():
    raw_body = request.get_data()  # bytes — NOT request.get_json()
    sig = request.headers.get('X-EZ2257-Signature', '')
    if not verify(raw_body, sig):
        abort(401)
    event = json.loads(raw_body)
    if event['type'] == 'solo_account.verified':
        grant_upload_access(event['data']['externalUserId'])
    return 'ok'
$rawBody = file_get_contents('php://input'); // raw — NOT json_decode first
$sig = $_SERVER['HTTP_X_EZ2257_SIGNATURE'] ?? '';
$parts = array_column(array_map(fn($p) => explode('=', $p, 2), explode(',', $sig)), 1, 0);
$expected = hash_hmac('sha256', $parts['t'] . '.' . $rawBody, getenv('EZ2257_WEBHOOK_SECRET'));
if (!hash_equals($expected, $parts['v1'] ?? '')) {
    http_response_code(401); exit('Unauthorized');
}
$event = json_decode($rawBody, true);
if ($event['type'] === 'solo_account.verified') {
    grantUploadAccess($event['data']['externalUserId']);
}
echo 'ok';

When type === "solo_account.verified", grant upload access. You'll also receive solo_account.suspended — revoke upload access when it arrives.

3. Log every upload

Register each piece of content after a creator uploads it. A non-2xx here means the depiction has no COR record — treat it as an alertable error, not a warning.

curl -X POST https://easy2257.com/api/v1/solo-accounts/ACCOUNT_ID/content \
  -H "Authorization: Bearer $EZ2257_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "externalContentId": "video_abc123",
    "contentUrl": "https://your-cdn.com/video_abc123.mp4",
    "contentType": "video"
  }'
await fetch(`https://easy2257.com/api/v1/solo-accounts/${accountId}/content`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.EZ2257_API_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': externalContentId,
  },
  body: JSON.stringify({
    externalContentId: 'video_abc123',
    contentUrl: 'https://your-cdn.com/video_abc123.mp4',
    contentType: 'video',
  }),
});
requests.post(
    f'https://easy2257.com/api/v1/solo-accounts/{account_id}/content',
    headers={'Authorization': f'Bearer {os.environ["EZ2257_API_KEY"]}',
             'Idempotency-Key': external_content_id},
    json={'externalContentId': 'video_abc123',
          'contentUrl': 'https://your-cdn.com/video_abc123.mp4',
          'contentType': 'video'},
).raise_for_status()
$ch = curl_init("https://easy2257.com/api/v1/solo-accounts/{$accountId}/content");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('EZ2257_API_KEY'),
        'Content-Type: application/json',
        'Idempotency-Key: ' . $externalContentId,
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'externalContentId' => 'video_abc123',
        'contentUrl' => 'https://your-cdn.com/video_abc123.mp4',
        'contentType' => 'video',
    ]),
]);
curl_exec($ch);

Idempotent on externalContentId — calling twice with the same ID returns the existing record.

fileHash (SHA-256 of the binary) is optional but recommended — satisfies 28 CFR 75.2(f) record integrity. Omit it if you only have the URL.

4. Handle takedowns

When a creator removes content from your platform:

curl -X DELETE \
  "https://easy2257.com/api/v1/solo-accounts/ACCOUNT_ID/content/video_abc123" \
  -H "Authorization: Bearer $EZ2257_API_KEY"
await fetch(
  `https://easy2257.com/api/v1/solo-accounts/${accountId}/content/${externalContentId}`,
  { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.EZ2257_API_KEY}` } }
);
requests.delete(
    f'https://easy2257.com/api/v1/solo-accounts/{account_id}/content/{external_content_id}',
    headers={'Authorization': f'Bearer {os.environ["EZ2257_API_KEY"]}'},
).raise_for_status()
$ch = curl_init("https://easy2257.com/api/v1/solo-accounts/{$accountId}/content/{$externalContentId}");
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'DELETE',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('EZ2257_API_KEY')],
]);
curl_exec($ch);

You'll also receive a content_log.deleted webhook if Easy2257 initiates the removal. Per the TAKE IT DOWN Act, remove the content from your CDN within 48 hours of receiving this event.

You're done.

What you built2257 obligation satisfied
POST /v1/solo-accountsInitiates ID record for the performer
Onboarding redirectProducer attestation + ID verification (§ 2257(b)(1))
solo_account.verified webhookConfirms ID is on file at Easy2257 (COR)
POST /v1/solo-accounts/{id}/contentPer-depiction audit trail (§ 2257(b)(1))
DELETE + content_log.deletedTakedown compliance (TAKE IT DOWN Act)

Full endpoint reference → API Reference

On this page