FinBaze for Platforms

Guides

FinBaze never emails your users on its own for a company on your platform. It creates a message, routes it by who should deliver it, and tells you. This guide is the working path; Messages has the full rules and the message catalogue lists every type.

1. Subscribe#

Create a webhook endpoint with message.created and message.unacknowledged (see Events and webhooks):

mutation {
  createPlatformWebhookEndpoint(input: {
    url: "https://example.com/finbaze"
    eventTypes: ["message.created", "message.unacknowledged"]
  }) { endpoint { id } signingSecret }
}

signingSecret is shown once. Verify every delivery with it (receivers in Node, Python and PHP are in the sample apps).

2. Deliver#

data.object of message.created is the message. It already has the text for each channel (content.push, content.in_app, content.email, content.sms) and a deep_link your app routes to. Deliver in your own channel, then acknowledge:

mutation Ack($id: ID!) {
  acknowledgeMessage(input: { id: $id, channel: "push", outcome: "delivered" }) { id status acknowledgedAt }
}

Dedupe on dedupe_key; do not deliver after expires_at. Acknowledge at least the messages with ack_required: critical messages and those that serve as evidence (invoices and reminders you deliver). Use acknowledgeMessages for up to 100. outcome: "failed" tells FinBaze you could not deliver; the message stays unacknowledged and you get message.unacknowledged after ack_deadline.

3. Choose who sends what#

mutation {
  setPlatformMessageRouting(input: { scope: "category", category: "tax_deadline", routing: "partner_delivers" }) { id routing }
}

routing is partner_delivers (the default for notifications), finbaze_sends, both or suppress. A rule applies to everything (scope: "all"), one category or one type, for the platform or for one company (profileId). The most specific rule wins. finbaze_sends needs a verified sender domain for your platform; without one the message falls back to partner_delivers. platformMessageRouting lists your rules, and clearPlatformMessageRouting removes one.

Lifecycle, billing and FinBaze account-security messages are never delivered for platform companies and cannot be changed (ROUTING_NOT_CHANGEABLE).

4. Settings#

mutation {
  updatePlatformMessageSettings(input: { quietHoursStart: "22:00", quietHoursEnd: "07:30", quietHoursTimezone: "Europe/Amsterdam", digestHourLocal: 8, brandName: "Acme Books" }) {
    digestHourLocal quietHoursStart brandName
  }
}

Critical messages ignore quiet hours and limits. More than 20 messages per person per hour, or 3 of one type, fold into an hourly digest.

5. Read the log#

query { platformMessages(filter: { unacknowledgedOnly: true, limit: 50 }) { id type priority status routing routingReason ackDeadline } }

routingReason tells you why: catalogue_default, platform_category, company_override, hard_rule, feature_hidden. resendPlatformMessage queues a message again.

Push notifications#

FinBaze sends push only to its own mobile app, for companies that are not on a platform. For a platform company you send the push yourself from message.created. On mobile, the push can carry an intent that opens the right component; see Mobile.

Pitfalls#

  • Treating a webhook delivery as unique: deliveries are at least once.
  • Forgetting to acknowledge critical messages: after the deadline FinBaze raises message.unacknowledged, and with criticalEmailFallback on it may email the user itself.
  • Routing to finbaze_sends without a verified sender domain.