https://metaboost.vercel.app
📊 View Metadata

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

POST /payment-metadata

Create new payment metadata. Store payment information from any payment type including amount, sender, boost message, and podcast episode attribution.

Request Body REQUIRED
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)
Example: Boost Payment
{
  "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.

Response 201
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "updateToken": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "self": "https://metaboost.vercel.app/payment-metadata/550e8400-e29b-41d4-a716-446655440000"
}
GET /payment-metadata/{id}

Retrieve payment metadata by its unique identifier.

Path Parameters
Parameter Type Description
id string UUID of the payment metadata
Response 200
{
  "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"
}
GET /payment-metadata/list

List all payment metadata with pagination support.

Query Parameters
Parameter Type Description
cursor string Pagination cursor for next page
limit number Number of items per page (default: 100, max: 1000)
Response 200
{
  "items": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "type": "bitcoin-lightning",
      "metadata": { ... },
      "podcastGuid": "...",
      "rssItemGuid": "..."
    }
  ],
  "cursor": "next-page-cursor",
  "hasMore": true
}
GET /payment-metadata/findByRSSItem

Find all payment metadata for a specific podcast episode. Use this to display all boosts and payments received for an episode.

Query Parameters
Parameter Type Required Description
podcastGuid string Podcast GUID
rssItemGuid string Episode GUID
Example Request
GET /payment-metadata/findByRSSItem?podcastGuid=9b024349-ccf0-5f69-a609-6b82873eab3c&rssItemGuid=episode-123
Response 200
{
  "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"
    }
  ]
}
PUT /payment-metadata?updateToken={token}

Update existing payment metadata. Requires the updateToken returned during creation.

Query Parameters
Parameter Type Required Description
updateToken string Update token from creation response
Response 200
{
  "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/{id}?updateToken={token}

Delete payment metadata. Requires the updateToken returned during creation.

Path Parameters
Parameter Type Description
id string UUID of the payment metadata
Query Parameters
Parameter Type Required Description
updateToken string Update token from creation response
Response 200
{
  "success": true
}

Schemas

PaymentMetadata

id string (uuid) REQUIRED
Unique identifier for the payment metadata
type string REQUIRED
Payment type. Common values: "bitcoin-lightning", "monero", or any other payment type
metadata object REQUIRED
Metadata object containing payment details - same structure for all payment types
signature string OPTIONAL
Cryptographic signature for the metadata object (hex encoded)

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.

Based on Podcast TLV: This API uses the Podcast TLV specification to define the metadata object structure. See the Podcast TLV specification for complete field documentation.

Metadata Fields

These fields are payment-type agnostic and work the same way for all payment types.

📖 Full Specification: See the complete Podcast TLV documentation for all available fields, their types, and usage requirements.

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"
}
Flexible Structure: You can add any custom fields relevant to your application. The API does not enforce a strict schema, but using consistent field names across payment types makes your data easier to query and display.

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.

⚠️ Critical: JSON field order affects signatures! You MUST canonicalize (sort keys alphabetically) before signing/verifying.

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

  1. Canonicalize the metadata JSON (sorted keys alphabetically, recursively, no whitespace)
  2. Hash the canonical string (e.g., SHA-256)
  3. Sign the hash with your private key (secp256k1, ed25519, etc.)
  4. 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