Herald LogoHerald Docs
Api

REST API Reference

Direct HTTP integration for the Herald Notification Gateway.

REST API Reference

The Herald Notification Gateway provides a RESTful API for protocols that prefer direct HTTP calls over the SDK.

Authentication

All requests (except /health) must include a Bearer token in the Authorization header.

Authorization: Bearer hrld_live_your_key_here

Notifications

Send Notification

POST /v1/notify

Queues a notification for a specific wallet address.

Request Body:

{
  "wallet": "7xR4mKp2nQ...",
  "subject": "Liquidation Warning",
  "body": "Your health factor dropped to 1.03.",
  "category": "defi",
  "priority": "critical",
  "preferredChannel": "telegram",
  "receipt": true,
  "idempotencyKey": "liq_7xR4_1716000000000"
}
FieldTypeRequiredDescription
walletstringYesSolana wallet address
subjectstringYesNotification subject line
bodystringYesNotification body (Markdown supported)
categorystringYesdefi | governance | system | marketing | security
prioritystringNonormal | important | criticalcritical adds SMS fallback
preferredChannelstringNoemail | telegram | sms — hint only
receiptbooleanNoWrite ZK proof of delivery on-chain (default false)
templateIdstringNoEmail template ID (Growth tier+)
templateVariablesobjectNoVariables to inject into the template
idempotencyKeystringNoPrevents duplicate sends

Response (202 Accepted):

{
  "notificationId": "01HWXYZ...",
  "status": "queued",
  "recipientRegistered": true,
  "estimatedDeliveryMs": 3200,
  "deliveryChannel": "telegram",
  "environment": "production"
}
FieldDescription
statusqueued | opted_out | duplicate | failed
recipientRegisteredWhether the wallet is registered with Herald
estimatedDeliveryMsEstimated time to delivery in milliseconds
deliveryChannelChannel selected for delivery

Bulk Notification

POST /v1/notify/bulk

Send up to 100 notifications in a single request.

Request Body:

{
  "notifications": [
    {
      "wallet": "7xR4mKp2nQ...",
      "subject": "Governance Proposal Live",
      "body": "Vote on proposal #42 before it closes.",
      "category": "governance",
      "receipt": true,
      "idempotencyKey": "prop42:7xR4mKp2nQ"
    },
    {
      "wallet": "9yG2nRp4xQ...",
      "subject": "Governance Proposal Live",
      "body": "Vote on proposal #42 before it closes.",
      "category": "governance",
      "receipt": true,
      "idempotencyKey": "prop42:9yG2nRp4xQ"
    }
  ]
}

Response (202 Accepted):

{
  "results": [
    {
      "notificationId": "01HWXYZ...",
      "status": "queued",
      "recipientRegistered": true,
      "estimatedDeliveryMs": 3200,
      "deliveryChannel": "email"
    },
    {
      "notificationId": "01HWYZA...",
      "status": "opted_out",
      "recipientRegistered": true,
      "estimatedDeliveryMs": null,
      "deliveryChannel": null
    }
  ]
}

Results are returned in the same order as the input notifications array.


Get Notification Status

GET /v1/notifications/:id

Retrieves the current delivery and receipt status of a notification.

Response (200 OK):

{
  "notification_id": "01HWXYZ...",
  "wallet": "7xR4mKp2nQ...",
  "subject": "Liquidation Warning",
  "status": "delivered",
  "delivery_channel": "telegram",
  "delivered_at": "2026-05-17T12:34:56Z",
  "created_at": "2026-05-17T12:34:50Z",
  "write_receipt": true,
  "receipt_status": "confirmed",
  "receipt_tx": "5xK9mPqR...",
  "receipt_failure_reason": null,
  "last_receipt_attempt_at": "2026-05-17T12:35:10Z"
}

receipt_status values:

ValueMeaning
pendingNotification delivered — ZK receipt not yet written on-chain
confirmedReceipt landed on Solana (receipt_tx is populated)
failedOn-chain write failed — see receipt_failure_reason
disabledReceipt was not requested for this notification

List Notifications

GET /v1/notifications

Returns paginated notifications for the authenticated protocol.

Query Parameters:

  • page (optional, default 1)
  • limit (optional, default 20, max 100)

Response (200 OK):

{
  "data": [
    {
      "notification_id": "01HWXYZ...",
      "wallet": "7xR4mKp2nQ...",
      "subject": "Liquidation Warning",
      "status": "delivered",
      "delivery_channel": "email",
      "receipt_status": "confirmed",
      "receipt_tx": "5xK9mP...",
      "created_at": "2026-05-17T12:34:56Z"
    }
  ],
  "total": 142,
  "page": 1
}

Preview Notification

POST /v1/preview

Render how a notification will look before sending. Useful for template testing.

Request Body:

{
  "wallet": "7xR4mKp2nQ...",
  "subject": "Your weekly summary",
  "body": "## Summary\n\nHere are your stats...",
  "category": "defi",
  "templateId": "tmpl_xxx"
}

Response (200 OK):

{
  "renderedHtml": "<html>...</html>",
  "telegramText": "*Your weekly summary*\n\nHere are your stats...",
  "smsText": "Your weekly summary: Here are your stats..."
}

Schedules

Schedule One-Time Send

POST /v1/schedule

Request Body:

{
  "wallet": "7xR4mKp2nQ...",
  "subject": "Your staking rewards are ready",
  "body": "You have 12.4 SOL in unclaimed staking rewards.",
  "category": "defi",
  "scheduledFor": "2026-06-01T09:00:00Z",
  "timezone": "America/New_York"
}

Response (201 Created):

{
  "id": "sched_01H...",
  "status": "PENDING",
  "nextRunAt": "2026-06-01T13:00:00Z"
}

Schedule Recurring (Cron)

POST /v1/schedule/cron

Request Body:

{
  "wallet": "7xR4mKp2nQ...",
  "subject": "Weekly portfolio summary",
  "body": "Here is your weekly DeFi activity report.",
  "category": "defi",
  "cronExpr": "0 9 * * 1",
  "timezone": "UTC"
}

Response (201 Created):

{
  "id": "sched_01H...",
  "status": "PENDING",
  "nextRunAt": "2026-05-26T09:00:00Z"
}

List Schedules

GET /v1/schedule

Query Parameters: page, limit

Response (200 OK):

{
  "items": [
    {
      "id": "sched_01H...",
      "wallet": "7xR4mKp2nQ...",
      "subject": "Weekly portfolio summary",
      "status": "PENDING",
      "nextRunAt": "2026-05-26T09:00:00Z"
    }
  ],
  "total": 3
}

Cancel Schedule

DELETE /v1/schedule/:id

Response (200 OK):

{ "cancelled": true }

Webhooks

Register Webhook

POST /v1/webhooks

Request Body:

{
  "url": "https://myprotocol.com/webhooks/herald",
  "events": ["notification.delivered", "notification.failed", "notification.bounced"]
}

Response (201 Created):

{
  "id": "wh_01HX...",
  "url": "https://myprotocol.com/webhooks/herald",
  "events": ["notification.delivered", "notification.failed", "notification.bounced"],
  "isActive": true,
  "secret": "whsec_xxx..."
}

The secret is shown once — store it immediately.


List Webhooks

GET /v1/webhooks

Response (200 OK):

[
  {
    "id": "wh_01HX...",
    "url": "https://myprotocol.com/webhooks/herald",
    "events": ["notification.delivered"],
    "isActive": true
  }
]

Update Webhook

PATCH /v1/webhooks/:id

Request Body:

{
  "events": ["notification.delivered", "notification.bounced"],
  "isActive": true
}

Response (200 OK): Updated webhook object.


Test Webhook

POST /v1/webhooks/:id/test

Sends a synthetic notification.delivered event to verify connectivity.

Response (200 OK):

{ "sent": true }

Delete Webhook

DELETE /v1/webhooks/:id

Response (204 No Content)


Email Templates

Custom templates require Growth tier or above.

Create Template

POST /v1/templates/email

Request Body:

{
  "name": "Liquidation Alert",
  "category": "defi",
  "subjectTemplate": "Your {{protocol}} position is at risk",
  "htmlSource": "<h1>Alert</h1><p>Health factor: {{healthFactor}}</p>",
  "textSource": "Health factor: {{healthFactor}}",
  "heraldFooter": "minimal"
}

Response (201 Created):

{ "templateId": "tmpl_01HX..." }

List Templates

GET /v1/templates/email

Response (200 OK):

{
  "data": [
    {
      "id": "tmpl_01HX...",
      "name": "Liquidation Alert",
      "category": "defi",
      "createdAt": "2026-05-01T00:00:00Z"
    }
  ]
}

Get Template

GET /v1/templates/email/:id


Update Template

PUT /v1/templates/email/:id

Creates a new version of the template.


Delete Template

DELETE /v1/templates/email/:id

Response (204 No Content)


Analytics

Delivery Analytics

GET /v1/analytics

Query Parameters:

  • period7d | 30d | 90d (default 30d)

Response (200 OK):

{
  "period": "30d",
  "total_sends": 4521,
  "delivery_rate": 0.993,
  "bounce_rate": 0.002,
  "breakdown": {
    "delivered": 4489,
    "failed": 14,
    "opted_out": 12,
    "bounced": 6
  }
}

Engagement Metrics

GET /v1/engagement

Query Parameters: startDate, endDate (ISO 8601 dates)

Response (200 OK):

{
  "openRate": 0.412,
  "clickRate": 0.089,
  "unsubscribeRate": 0.003
}

Audience Insights

GET /v1/audience

Response (200 OK):

{
  "totalRegistered": 18420,
  "broadcastableSubscribers": 4200,
  "channelCoverage": {
    "email": 92,
    "telegram": 41,
    "sms": 12
  },
  "registrationTrend": [
    { "date": "2026-05-01", "count": 42 },
    { "date": "2026-05-02", "count": 67 }
  ]
}

Protocol Info

GET /v1/protocols/me

Response (200 OK):

{
  "id": "proto_01HX...",
  "name": "My Protocol",
  "tier": 1,
  "tier_name": "Growth",
  "sends_this_period": 12450,
  "subscription_expires_at": "2026-06-01T00:00:00Z"
}
tierName
0Developer
1Growth
2Scale
3Enterprise

Usage

GET /v1/usage

Response (200 OK):

{
  "tier": 1,
  "usage": 12450,
  "remaining": 37550,
  "overageEnabled": false,
  "resetsAt": "2026-06-01T00:00:00Z"
}

API Request Log

GET /v1/requests

Query Parameters:

  • page (default 1)
  • limit (default 50, max 100)
  • statusCode — filter by HTTP status (optional)
  • endpoint — filter by path substring (optional)

Response (200 OK):

{
  "items": [
    {
      "method": "POST",
      "endpoint": "/v1/notify",
      "statusCode": 202,
      "latencyMs": 84,
      "timestamp": "2026-05-17T12:34:56Z"
    }
  ],
  "total": 320,
  "page": 1
}

Subscriptions

Subscribe Wallet

POST /v1/subscriptions

{
  "walletAddress": "7xR4mKp2nQ...",
  "channels": ["email", "telegram"]
}

Check Subscription

GET /v1/subscriptions/:wallet

{
  "subscribed": true,
  "channels": ["email"],
  "subscribedAt": "2026-05-01T00:00:00Z"
}

Unsubscribe Wallet

DELETE /v1/subscriptions/:wallet


Broadcast

POST /v1/broadcast

Requires Growth tier or above.

Request Body:

{
  "subject": "Governance Vote: Protocol Upgrade v2",
  "body": "A governance proposal is live. Vote before May 24.",
  "category": "governance",
  "receipt": false
}

Response (202 Accepted):

{
  "broadcast_id": "bcast_01H...",
  "queued_count": 4200,
  "total_subscribers": 4200,
  "estimated_delivery_s": 120
}

Health Check

GET /health

No authentication required.

{ "status": "ok", "timestamp": "2026-05-17T12:00:00Z" }

Rate Limits

TierRequests/secMonthly Sends
Developer21,000
Growth2050,000
Scale100250,000
Enterprise5001,000,000

Rate limit headers are included in every response:

X-RateLimit-Limit: 20
X-RateLimit-Remaining: 19
X-RateLimit-Reset: 1716000060

Error Responses

All errors follow a consistent shape:

{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "wallet is required",
  "code": "VALIDATION_ERROR"
}

Common error codes:

CodeHTTPDescription
INVALID_API_KEY401Missing or malformed API key
INSUFFICIENT_QUOTA402Monthly send limit reached
WALLET_NOT_REGISTERED404Wallet has no Herald identity
DUPLICATE_IDEMPOTENCY_KEY409Notification already sent with this key
RATE_LIMIT_EXCEEDED429Too many requests
TEMPLATE_NOT_FOUND404Template ID does not exist

On this page