Recipes
Self-contained end-to-end recipes. Each one is a runnable sequence of nak and curl commands assuming $NSEC (your private key) is set in the environment.
Don’t leak
$NSECto shell history. Either prefix commands with a space (withHISTCONTROL=ignorespace) orunset NSECafter each session.
Stream a test pattern via Shosho Server
End-to-end: create a Nostr identity, get streaming credentials from Shosho Server, go live with an FFmpeg test pattern, post to chat, then end the stream cleanly.
# Step 1: Create identity (skip if user already has one)
NSEC=$(nak key generate)
PUBKEY=$(nak key public $NSEC)
NPUB=$(nak encode npub $PUBKEY)
echo "Created user: $NPUB"
# Step 2: Publish relay list (kind 10002)
nak event -k 10002 \
-t r=wss://relay.damus.io \
-t r='wss://relay.primal.net;write' \
--sec $NSEC \
wss://relay.damus.io wss://relay.primal.net wss://purplepag.es
# Step 3: Publish profile (kind 0)
nak event -k 0 \
--content '{"name":"AI Test Streamer","about":"Test stream from an AI agent","picture":"https://robohash.org/'$PUBKEY'.png"}' \
--sec $NSEC \
wss://relay.damus.io wss://relay.primal.net wss://purplepag.es
# Step 4: Get Shosho Server account info
AUTH=$(nak event -k 27235 \
-t u=https://api.shosho.live/api/v1/account \
-t method=GET \
--sec $NSEC < /dev/null 2>/dev/null | base64)
ACCOUNT=$(curl -s -H "Authorization: Nostr $AUTH" \
https://api.shosho.live/api/v1/account)
echo "Account: $ACCOUNT"
# Step 5: Accept terms of service (if not already accepted)
AUTH=$(nak event -k 27235 \
-t u=https://api.shosho.live/api/v1/account \
-t method=PATCH \
--sec $NSEC < /dev/null 2>/dev/null | base64)
curl -X PATCH -H "Authorization: Nostr $AUTH" \
-H "Content-Type: application/json" \
-d '{"accept_tos": true}' \
https://api.shosho.live/api/v1/account
# Step 6: Set stream metadata (title, summary)
AUTH=$(nak event -k 27235 \
-t u=https://api.shosho.live/api/v1/event \
-t method=PATCH \
--sec $NSEC < /dev/null 2>/dev/null | base64)
curl -X PATCH -H "Authorization: Nostr $AUTH" \
-H "Content-Type: application/json" \
-d '{"title": "AI Test Stream", "summary": "Testing live streaming from an AI agent"}' \
https://api.shosho.live/api/v1/event
# Step 7: Extract ingest URL and stream key from the account response
# e.g. INGEST_URL="rtmp://ingest.shosho.live:1935/Basic" STREAM_KEY="<key>"
INGEST_URL=$(echo "$ACCOUNT" | python3 -c "import sys,json; print(json.load(sys.stdin)['endpoints'][0]['url'])")
STREAM_KEY=$(echo "$ACCOUNT" | python3 -c "import sys,json; print(json.load(sys.stdin)['endpoints'][0]['key'])")
# Step 8: Start FFmpeg test pattern in the background
ffmpeg -f lavfi -i testsrc=size=1280x720:rate=30 \
-f lavfi -i sine=frequency=440:sample_rate=44100 \
-vcodec libx264 -preset veryfast -b:v 2500k \
-acodec aac -b:a 128k \
-f flv "$INGEST_URL/$STREAM_KEY" &
FFMPEG_PID=$!
echo "FFmpeg started with PID: $FFMPEG_PID"
# Step 9: Poll for the live event to appear on the network
sleep 15
for i in $(seq 1 12); do
LIVE_EVENT=$(nak req -k 30311 -t 'p='$PUBKEY -l 1 wss://relay.damus.io 2>/dev/null)
if echo "$LIVE_EVENT" | grep -q '"status","live"'; then
echo "Stream is live!"
break
fi
echo "Waiting for stream event... (attempt $i)"
sleep 10
done
# Step 10: Build the stream URL
D_TAG=$(echo "$LIVE_EVENT" | python3 -c "import sys,json; e=json.load(sys.stdin); print(next(t[1] for t in e['tags'] if t[0]=='d'))")
AUTHOR=$(echo "$LIVE_EVENT" | python3 -c "import sys,json; print(json.load(sys.stdin)['pubkey'])")
NADDR=$(nak encode naddr -k 30311 -d $D_TAG -a $AUTHOR -r wss://relay.damus.io < /dev/null)
echo "You are live! Watch at: https://shosho.live/live/$NADDR"
# Step 11: Post a chat message
nak event -k 1311 \
--content "Hello from my AI agent! This is an automated test stream." \
-t a="30311:$AUTHOR:$D_TAG;wss://relay.damus.io;root" \
--sec $NSEC \
wss://relay.damus.io wss://relay.primal.net
# Step 12: Stop FFmpeg
kill $FFMPEG_PID
echo "FFmpeg stopped"
# Step 13: Wait for Shosho Server to publish status: ended
sleep 15
for i in $(seq 1 12); do
ENDED_EVENT=$(nak req -k 30311 -t 'p='$PUBKEY -l 1 wss://relay.damus.io 2>/dev/null)
if echo "$ENDED_EVENT" | grep -q '"status","ended"'; then
echo "Stream ended successfully!"
break
fi
echo "Waiting for ended status... (attempt $i)"
sleep 10
done
echo "Done. Replay (if recorded): https://shosho.live/live/$NADDR"Go live via the Show Chat API
Use this when you already have an HLS playback URL from your own server (or a third-party hosted service) and want Shosho to manage the Nostr live event for you. See Going live → Option C.
# Replace with the HLS URL of your existing stream
HLS_URL="https://your-server.com/stream.m3u8"
D_TAG="my-show-$(date +%s)"
# Step 1: Create the show-chat (publishes the kind 30311 with status: live)
AUTH=$(nak event -k 27235 \
-t u=https://shosho.live/api/show-chat/create \
-t method=POST \
--sec $NSEC < /dev/null 2>/dev/null | base64)
RESP=$(curl -s -X POST -H "Authorization: Nostr $AUTH" \
-H "Content-Type: application/json" \
-d '{
"dTag": "'"$D_TAG"'",
"hlsUrl": "'"$HLS_URL"'",
"title": "My Live Stream",
"summary": "Going live via Show Chat",
"image": "https://robohash.org/'$(nak key public $NSEC)'.png"
}' \
https://shosho.live/api/show-chat/create)
NADDR=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['naddr'])")
echo "Live at: https://shosho.live/live/$NADDR"
# Step 2: Stream is now live. The Shosho backend will poll the HLS URL
# every minute and republish the 30311 every 15 minutes automatically.
# Step 3: When you're done, end the stream cleanly
AUTH=$(nak event -k 27235 \
-t u=https://shosho.live/api/show-chat/update \
-t method=POST \
--sec $NSEC < /dev/null 2>/dev/null | base64)
curl -X POST -H "Authorization: Nostr $AUTH" \
-H "Content-Type: application/json" \
-d '{"dTag": "'"$D_TAG"'", "status": "ended"}' \
https://shosho.live/api/show-chat/update
echo "Stream ended."Post a clip from a video file
Upload a video to nostr.build, then publish a NIP-71 clip event (kind 34236 for short/vertical, 34235 for normal/horizontal).
# Step 1: Upload video to nostr.build
AUTH=$(nak event -k 27235 \
-t u=https://nostr.build/api/v2/nip96/upload \
-t method=POST \
--sec $NSEC < /dev/null 2>/dev/null | base64)
UPLOAD=$(curl -s -X POST \
-H "Authorization: Nostr $AUTH" \
-F "file=@clip.mp4" \
https://nostr.build/api/v2/nip96/upload)
# Extract URL, sha256, mime, dimensions, and thumbnail from the response.
# Adjust to your local jq / python preference.
VIDEO_URL=$(echo "$UPLOAD" | python3 -c "import sys,json; r=json.load(sys.stdin); imeta=next(t for t in r['nip94_event']['tags'] if t[0]=='url'); print(imeta[1])")
THUMB_URL=$(echo "$UPLOAD" | python3 -c "import sys,json; r=json.load(sys.stdin); t=next((t for t in r['nip94_event']['tags'] if t[0]=='thumb'),None); print(t[1] if t else '')")
SHA256=$(echo "$UPLOAD" | python3 -c "import sys,json; r=json.load(sys.stdin); t=next((t for t in r['nip94_event']['tags'] if t[0]=='x'),None); print(t[1] if t else '')")
MIME=$(echo "$UPLOAD" | python3 -c "import sys,json; r=json.load(sys.stdin); t=next((t for t in r['nip94_event']['tags'] if t[0]=='m'),None); print(t[1] if t else 'video/mp4')")
DIM=$(echo "$UPLOAD" | python3 -c "import sys,json; r=json.load(sys.stdin); t=next((t for t in r['nip94_event']['tags'] if t[0]=='dim'),None); print(t[1] if t else '')")
# Step 2: Publish clip (kind 34236 = short/vertical, 34235 = normal/horizontal)
CLIP_D="my-clip-$(date +%s)"
PUBKEY=$(nak key public $NSEC)
nak event -k 34236 \
--content "A clip from my stream — full description goes in content." \
-t d=$CLIP_D \
-t title="My Clip" \
-t alt="Short video clip" \
-t duration=30 \
-t published_at=$(date +%s) \
-t "imeta=url $VIDEO_URL;m $MIME;dim $DIM;x $SHA256;image $THUMB_URL;service nip96" \
-t t=shosho \
--sec $NSEC \
wss://relay.damus.io wss://relay.primal.net
NADDR=$(nak encode naddr -k 34236 -d $CLIP_D -a $PUBKEY < /dev/null)
echo "Clip posted: https://shosho.live/clips/$NADDR"List a product for sale
Upload a product image, then publish a NIP-99 classified listing (kind 30402).
# Step 1: Upload product image to nostr.build
AUTH=$(nak event -k 27235 \
-t u=https://nostr.build/api/v2/nip96/upload \
-t method=POST \
--sec $NSEC < /dev/null 2>/dev/null | base64)
UPLOAD=$(curl -s -X POST \
-H "Authorization: Nostr $AUTH" \
-F "file=@product-photo.jpg" \
https://nostr.build/api/v2/nip96/upload)
IMAGE_URL=$(echo "$UPLOAD" | python3 -c "import sys,json; r=json.load(sys.stdin); imeta=next(t for t in r['nip94_event']['tags'] if t[0]=='url'); print(imeta[1])")
# Step 2: Publish product listing
PRODUCT_D="product-$(date +%s)"
PUBKEY=$(nak key public $NSEC)
NPUB=$(nak encode npub $PUBKEY)
nak event -k 30402 \
--content "Full product description in **markdown**. Supports *italic*, [links](https://example.com), and lists." \
-t d=$PRODUCT_D \
-t title="My Product" \
-t 'price=21000;sats' \
-t summary="Short tagline for the product" \
-t published_at=$(date +%s) \
-t location="Worldwide shipping" \
-t image=$IMAGE_URL \
-t t=nostr \
-t status=active \
--sec $NSEC \
wss://relay.damus.io wss://relay.primal.net
echo "Product listed."
echo "Shop: https://shosho.live/profile/$NPUB?tab=shop"
echo "Product: https://shosho.live/product/$NPUB-$PRODUCT_D"Related
- Going live programmatically — the three streaming options in detail
- Live event structure (kind 30311)
- Clips (kind 34235 / 34236)
- Products (NIP-99)
- Media uploads
- NIP-98 HTTP authentication