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 nowimport 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 nowThe response is one of two things:
{ status: "active" }— creator already has Easy2257. Grant upload access immediately.{ status: "pending_id_verification", onboardingUrl }— redirect the creator toonboardingUrlin 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 built | 2257 obligation satisfied |
|---|---|
POST /v1/solo-accounts | Initiates ID record for the performer |
| Onboarding redirect | Producer attestation + ID verification (§ 2257(b)(1)) |
solo_account.verified webhook | Confirms ID is on file at Easy2257 (COR) |
POST /v1/solo-accounts/{id}/content | Per-depiction audit trail (§ 2257(b)(1)) |
DELETE + content_log.deleted | Takedown compliance (TAKE IT DOWN Act) |
Full endpoint reference → API Reference