MetaBoost API Specification
MetaBoost provides a REST API for storing and retrieving podcast payment metadata for any payment type (Bitcoin Lightning, Monero, or others).
Base URL
https://metaboost.vercel.app
Authentication
Authetnication is not required. Update and delete operations require an
updateToken that is returned when creating metadata.
Endpoints
Create new payment metadata. Store payment information from any payment type including amount, sender, boost message, and podcast episode attribution.
| Field | Type | Required | Description |
|---|---|---|---|
| type | string | ✓ | Payment type: "bitcoin-lightning", "monero", or any other payment type |
| metadata | object | ✓ | Metadata object containing payment details - same structure for all payment types |
| signature | string | Optional cryptographic signature for the entire payment metadata (hex encoded) |
{
"type": "bitcoin-lightning",
"metadata": {
"guid": "9b024349-ccf0-5f69-a609-6b82873eab3c",
"podcast": "Mere Mortals",
"feedID": 1844352,
"episode": "The Art Of NFT's & Aimless Wandering",
"episode_guid": "Buzzsprout-9931017",
"action": "boost",
"ts": 574,
"app_name": "Fountain",
"sender_name": "Alice",
"sender_id": "nSiq7id78JAdH9uY1pIy",
"value_msat_total": 100000,
"message": "Great episode on podcasting!"
},
"signature": "f1e2d3c4b5a6789012345678901234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef5678"
}
Note: The metadata structure follows Podcast TLV specification and is the same across all payment types.
Use type to indicate the payment type (bitcoin-lightning, monero, etc.),
and populate the metadata fields according to the Podcast TLV specification.
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"updateToken": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"self": "https://metaboost.vercel.app/payment-metadata/550e8400-e29b-41d4-a716-446655440000"
}
Retrieve payment metadata by its unique identifier.
| Parameter | Type | Description |
|---|---|---|
| id | string | UUID of the payment metadata |
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"type": "bitcoin-lightning",
"metadata": {
"guid": "9b024349-ccf0-5f69-a609-6b82873eab3c",
"podcast": "Mere Mortals",
"feedID": 1844352,
"episode": "The Art Of NFT's & Aimless Wandering",
"episode_guid": "Buzzsprout-9931017",
"action": "boost",
"ts": 574,
"app_name": "Fountain",
"sender_name": "Alice",
"value_msat_total": 100000,
"message": "Great episode!"
},
"signature": "f1e2d3c4b5a6789012345678901234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef5678"
}
List all payment metadata with pagination support.
| Parameter | Type | Description |
|---|---|---|
| cursor | string | Pagination cursor for next page |
| limit | number | Number of items per page (default: 100, max: 1000) |
{
"items": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"type": "bitcoin-lightning",
"metadata": { ... },
"podcastGuid": "...",
"rssItemGuid": "..."
}
],
"cursor": "next-page-cursor",
"hasMore": true
}
Find all payment metadata for a specific podcast episode. Use this to display all boosts and payments received for an episode.
| Parameter | Type | Required | Description |
|---|---|---|---|
| podcastGuid | string | ✓ | Podcast GUID |
| rssItemGuid | string | ✓ | Episode GUID |
GET /payment-metadata/findByRSSItem?podcastGuid=9b024349-ccf0-5f69-a609-6b82873eab3c&rssItemGuid=episode-123
{
"items": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"type": "bitcoin-lightning",
"metadata": {
"guid": "9b024349-ccf0-5f69-a609-6b82873eab3c",
"episode_guid": "Buzzsprout-9931017",
"action": "boost",
"value_msat_total": 100000,
"message": "Great episode!",
"sender_name": "Alice",
"app_name": "Fountain"
},
"signature": "f1e2d3c4b5a6789012345678901234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef5678"
}
]
}
Update existing payment metadata. Requires the updateToken returned during creation.
| Parameter | Type | Required | Description |
|---|---|---|---|
| updateToken | string | ✓ | Update token from creation response |
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"updateToken": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"self": "https://metaboost.vercel.app/payment-metadata/550e8400-e29b-41d4-a716-446655440000"
}
Delete payment metadata. Requires the updateToken returned during creation.
| Parameter | Type | Description |
|---|---|---|
| id | string | UUID of the payment metadata |
| Parameter | Type | Required | Description |
|---|---|---|---|
| updateToken | string | ✓ | Update token from creation response |
{
"success": true
}
Schemas
PaymentMetadata
Metadata Object
The metadata object follows the Podcast TLV specification. This is a unified,
payment-agnostic structure that works the same way for Lightning, Monero, or any other
payment type. The type field indicates which payment type was used, but the
metadata fields remain consistent across all payment types.
Metadata Fields
These fields are payment-type agnostic and work the same way for all payment types.
Example: Boost Payment (Lightning)
{
"type": "bitcoin-lightning",
"metadata": {
"guid": "9b024349-ccf0-5f69-a609-6b82873eab3c",
"podcast": "Mere Mortals",
"feedID": 1844352,
"episode": "The Art Of NFT's & Aimless Wandering",
"episode_guid": "Buzzsprout-9931017",
"action": "boost",
"ts": 574,
"app_name": "Fountain",
"sender_name": "Alice",
"sender_id": "nSiq7id78JAdH9uY1pIy",
"value_msat_total": 100000,
"message": "Love this episode!",
"boost_link": "https://fountain.fm/episode/14934154309"
},
"signature": "f1e2d3c4b5a6789012345678901234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef5678"
}
Example: Stream Payment (Monero)
{
"type": "monero",
"metadata": {
"guid": "9b024349-ccf0-5f69-a609-6b82873eab3c",
"podcast": "Mere Mortals",
"feedID": 1844352,
"episode": "The Art Of NFT's & Aimless Wandering",
"episode_guid": "Buzzsprout-9931017",
"action": "stream",
"ts": 1200,
"app_name": "Podverse",
"sender_name": "Bob",
"value_msat_total": 50000,
"speed": "1.5"
},
"signature": "e2f3a4b5c6d789012345678901234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef6789"
}
Cryptographic Signatures
Optionally sign payment metadata to prove authenticity. The API stores signatures but does NOT verify them - verification is the responsibility of the receiving application.
Why Canonicalization Matters
These two JSON objects are semantically identical but would produce different signatures without canonicalization:
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 2, a: 1 };
JSON.stringify(obj1); // '{"a":1,"b":2}'
JSON.stringify(obj2); // '{"b":2,"a":1}'
// ❌ Different strings = different signatures!
Signing Process
- Canonicalize the metadata JSON (sorted keys alphabetically, recursively, no whitespace)
- Hash the canonical string (e.g., SHA-256)
- Sign the hash with your private key (secp256k1, ed25519, etc.)
- Encode signature as hex string
Example: Canonical JSON Function
// JavaScript canonicalization function
function canonicalizeJSON(obj) {
if (obj === null) return 'null';
if (typeof obj !== 'object') return JSON.stringify(obj);
if (Array.isArray(obj)) {
return '[' + obj.map(canonicalizeJSON).join(',') + ']';
}
// CRITICAL: Sort keys alphabetically (recursively)
const sortedKeys = Object.keys(obj).sort();
const pairs = sortedKeys.map(key => {
return '"' + key + '":' + canonicalizeJSON(obj[key]);
});
return '{' + pairs.join(',') + '}';
}
// Test: Both produce same canonical output
const test1 = { z: 3, a: 1, m: 2 };
const test2 = { a: 1, m: 2, z: 3 };
console.log(canonicalizeJSON(test1)); // {"a":1,"m":2,"z":3}
console.log(canonicalizeJSON(test2)); // {"a":1,"m":2,"z":3}
// ✅ Same output = same signature!
Key Rules for Canonicalization
- Sort all keys alphabetically at every level (recursive)
- Arrays maintain order (don't sort array elements)
- Remove all whitespace between elements
- Use consistent formatting for numbers, strings, booleans, and null
- Both signer and verifier must use the same function