Content Verification#
Verification proves that you own the content you've published to ATProto. This is done through .well-known endpoints and <link> tags.
Why Verify?#
Verification allows platforms like Leaflet and WhiteWind to:
- Confirm you control the content you claim to have published
- Prevent impersonation
- Enable features that require ownership proof
- Build trust in the federated ecosystem
Quick Start#
1. Create .well-known Endpoint#
Create a SvelteKit endpoint at .well-known/site.standard.publication:
// src/routes/.well-known/site.standard.publication/+server.ts
import { text } from '@sveltejs/kit';
import { generatePublicationWellKnown } from 'svelte-standard-site/verification';
export function GET() {
return text(
generatePublicationWellKnown({
did: 'did:plc:your-did-here',
publicationRkey: '3abc123xyz789' // From publication creation
})
);
}
2. Verify It Works#
curl https://yourblog.com/.well-known/site.standard.publication
# Should output: at://did:plc:xxx/site.standard.publication/3abc123xyz789
3. Add Link Tag (Optional)#
Add verification to individual documents:
<!-- src/routes/blog/[slug]/+page.svelte -->
<script lang="ts">
import { generateDocumentLinkTag } from 'svelte-standard-site/verification';
const { data } = $props();
</script>
<svelte:head>
{@html generateDocumentLinkTag({
did: 'did:plc:xxx',
documentRkey: data.rkey
})}
</svelte:head>
Functions#
generatePublicationWellKnown#
Generate content for the .well-known endpoint:
import { generatePublicationWellKnown } from 'svelte-standard-site/verification';
const content = generatePublicationWellKnown({
did: 'did:plc:xxx',
publicationRkey: '3abc123xyz'
});
// Returns: "at://did:plc:xxx/site.standard.publication/3abc123xyz"
generateDocumentLinkTag#
Generate a <link> tag for a specific document:
import { generateDocumentLinkTag } from 'svelte-standard-site/verification';
const tag = generateDocumentLinkTag({
did: 'did:plc:xxx',
documentRkey: '3xyz789abc'
});
// Returns: '<link rel="site.standard.document" href="at://...">'
generatePublicationLinkTag#
Generate a <link> tag for your publication:
import { generatePublicationLinkTag } from 'svelte-standard-site/verification';
const tag = generatePublicationLinkTag({
did: 'did:plc:xxx',
publicationRkey: '3abc123xyz'
});
verifyPublicationWellKnown#
Programmatically verify a site's .well-known endpoint:
import { verifyPublicationWellKnown } from 'svelte-standard-site/verification';
const isValid = await verifyPublicationWellKnown(
'https://example.com',
'did:plc:xxx',
'3abc123xyz'
);
if (isValid) {
console.log('Site is verified!');
}
Complete Setup#
Get Your Publication Rkey#
When you create a publication, save the rkey:
import { StandardSitePublisher } from 'svelte-standard-site/publisher';
const publisher = new StandardSitePublisher({
identifier: 'you.bsky.social',
password: process.env.ATPROTO_APP_PASSWORD!
});
await publisher.login();
const result = await publisher.publishPublication({
name: 'My Blog',
url: 'https://yourblog.com'
});
// Save these!
console.log('DID:', publisher.getDid());
console.log('Rkey:', result.uri.split('/').pop());
Store in Environment Variables#
# .env
PUBLIC_ATPROTO_DID=did:plc:xxx
PUBLIC_PUBLICATION_RKEY=3abc123xyz
Create Endpoint#
// src/routes/.well-known/site.standard.publication/+server.ts
import { text } from '@sveltejs/kit';
import { generatePublicationWellKnown } from 'svelte-standard-site/verification';
import { PUBLIC_ATPROTO_DID, PUBLIC_PUBLICATION_RKEY } from '$env/static/public';
export function GET() {
return text(
generatePublicationWellKnown({
did: PUBLIC_ATPROTO_DID,
publicationRkey: PUBLIC_PUBLICATION_RKEY
}),
{
headers: {
'Content-Type': 'text/plain',
'Cache-Control': 'public, max-age=3600'
}
}
);
}
Add to Site Header#
<!-- src/routes/+layout.svelte -->
<script lang="ts">
import { generatePublicationLinkTag } from 'svelte-standard-site/verification';
import { PUBLIC_ATPROTO_DID, PUBLIC_PUBLICATION_RKEY } from '$env/static/public';
</script>
<svelte:head>
{@html generatePublicationLinkTag({
did: PUBLIC_ATPROTO_DID,
publicationRkey: PUBLIC_PUBLICATION_RKEY
})}
</svelte:head>
Document Verification#
For individual blog posts:
Store Document Rkeys#
When publishing, save the mapping:
// In your publish script
const result = await publisher.publishDocument({
// ... document data
});
// Save mapping: slug -> rkey
const mapping = {
'my-first-post': result.uri.split('/').pop(),
'another-post': '3xyz789abc'
// etc.
};
fs.writeFileSync('document-rkeys.json', JSON.stringify(mapping));
Add to Document Pages#
<!-- src/routes/blog/[slug]/+page.svelte -->
<script lang="ts">
import { generateDocumentLinkTag } from 'svelte-standard-site/verification';
import documentRkeys from '$lib/document-rkeys.json';
const { data } = $props();
const rkey = documentRkeys[data.slug];
</script>
<svelte:head>
{#if rkey}
{@html generateDocumentLinkTag({
did: 'did:plc:xxx',
documentRkey: rkey
})}
{/if}
</svelte:head>
AT-URI Utilities#
Build AT-URIs#
import { getDocumentAtUri, getPublicationAtUri } from 'svelte-standard-site/verification';
const docUri = getDocumentAtUri('did:plc:xxx', '3xyz789abc');
// "at://did:plc:xxx/site.standard.document/3xyz789abc"
const pubUri = getPublicationAtUri('did:plc:xxx', '3abc123xyz');
// "at://did:plc:xxx/site.standard.publication/3abc123xyz"
Parse AT-URIs#
import { parseAtUri } from 'svelte-standard-site/verification';
const parsed = parseAtUri('at://did:plc:xxx/site.standard.document/3xyz');
if (parsed) {
console.log(parsed.did); // "did:plc:xxx"
console.log(parsed.collection); // "site.standard.document"
console.log(parsed.rkey); // "3xyz"
}
Extract from HTML#
import { extractDocumentLinkFromHtml, extractPublicationLinkFromHtml } from 'svelte-standard-site/verification';
const html = await fetch('https://example.com/post').then((r) => r.text());
const docUri = extractDocumentLinkFromHtml(html);
// "at://did:plc:xxx/site.standard.document/3xyz"
const pubUri = extractPublicationLinkFromHtml(html);
// "at://did:plc:xxx/site.standard.publication/3abc"
Verification Flow#
- Publish a publication to ATProto
- Save the DID and rkey
- Create
.well-knownendpoint returning the AT-URI - Optionally add
<link>tags to your HTML - Platforms fetch your
.well-knownendpoint to verify ownership
sequenceDiagram
participant You
participant ATProto
participant Platform
You->>ATProto: Publish publication
ATProto->>You: Return AT-URI
You->>Your Site: Add .well-known endpoint
Platform->>Your Site: Fetch .well-known
Your Site->>Platform: Return AT-URI
Platform->>ATProto: Verify AT-URI exists
ATProto->>Platform: Confirmed
Platform->>Platform: Mark as verified ✓
Troubleshooting#
.well-known Returning 404#
SvelteKit requires special handling for .well-known:
// Option 1: Create the exact path
// src/routes/.well-known/site.standard.publication/+server.ts
// Option 2: Use static files
// static/.well-known/site.standard.publication
If using static files, make sure your hosting platform allows .well-known.
Wrong MIME Type#
Ensure you're returning text/plain:
export function GET() {
return text(content, {
headers: {
'Content-Type': 'text/plain'
}
});
}
CORS Issues#
If platforms can't access your endpoint:
export function GET() {
return text(content, {
headers: {
'Content-Type': 'text/plain',
'Access-Control-Allow-Origin': '*'
}
});
}
Verification Failing#
Use the verification utility to test:
const isValid = await verifyPublicationWellKnown(
'https://yourblog.com',
'did:plc:xxx',
'3abc123xyz'
);
if (!isValid) {
// Check:
// 1. .well-known endpoint is accessible
// 2. Returns exact AT-URI
// 3. No extra whitespace
// 4. Correct MIME type
}
Best Practices#
- Use environment variables - Don't hardcode DIDs/rkeys
- Add caching headers -
.well-knowncontent doesn't change often - Test before deploying - Verify the endpoint works
- Keep rkeys secure - Don't expose in client code unnecessarily
- Monitor - Check that verification keeps working after deploys
Hosting Platform Notes#
Vercel#
Works out of the box. No special configuration needed.
Netlify#
Add to netlify.toml:
[[redirects]]
from = "/.well-known/site.standard.publication"
to = "/.well-known/site.standard.publication/index.html"
status = 200
Cloudflare Pages#
Works by default. Consider adding a cache rule for .well-known.
GitHub Pages#
Static files work, but SvelteKit endpoints don't. Use the static file approach.