Skip to main content

Developer guide

Third-party push integration

The current request, authentication, idempotency, rate-limit, result, and security contract for server-to-server notification delivery.

Quick start

In Web Admin, create a third-party application for the target company. Copy its App ID and App Secret, and configure an optional Callback URL if final results are needed.

Engine creates notification-center entries for users selected by the optional audience (the whole eligible company by default) and attempts system Push only for eligible registered devices.

Endpoint and authentication

API environments:

Every server-to-server JSON request must include Authorization: Basic Base64(KEY:SECRET) using HTTP Basic Authentication, where KEY is the App ID and SECRET is the App Secret:

Endpoint: POST /openapi/v1/notifications.

bash
curl --request POST 'https://<your-engine-host>/openapi/v1/notifications' \
  --header 'Authorization: Basic <Base64(AppID:AppSecret)>' \
  --header 'Content-Type: application/json' \
  --data '{"requestId":"order-20260819-001","title":"Order completed","body":"Your order has been completed.","imageUrls":["https://cdn.example.com/order/001-1.webp","https://cdn.example.com/order/001-2.webp"],"displayFields":[{"label":"Store name","value":"Little Han Noodles (Wangjing)"},{"value":"ORD-10086"}],"tags":["Completed","Main store"]}'

Request contract

Field summary

FieldRequiredTypeMeaning
requestIdYesstringCaller-generated unique request identifier and idempotency key. Trimmed and must not be empty.
titleYesstringNotification title. Trimmed and must not be empty.
bodyYesstringNotification body. Trimmed and must not be empty.
imageUrlsNostring[]Image URLs shown in notification detail, in order.
displayFieldsNoobject[]Additional text fields shown in notification detail, in order.
tagsNostring[]Plain-text tags shown only in notification detail, in order.
audienceNoobjectNarrows delivery to users or company roles; omission means all eligible company users.

Images: imageUrls

  • Omit it, send null, or send [] for no images. Otherwise send 1-9 URLs in display order.
  • Every URL is trimmed, must be unique, and must be an absolute HTTPS URL without a username or password.
  • Each URL may contain at most 2048 Unicode characters. One invalid URL rejects the entire request.
  • Engine stores the URLs but never fetches, probes, proxies, or caches them.

Detail fields: displayFields

SubfieldRequiredLimitRendering
labelNoTrimmed, non-empty, at most 32 Unicode charactersShown as the field name.
valueYesTrimmed, non-empty, at most 256 Unicode charactersShown as the field value.
json
{
  "displayFields": [
    { "label": "Store name", "value": "Little Han Noodles (Wangjing)" },
    { "value": "ORD-10086" }
  ]
}
  • Omit it, send null, or send [] for no extra fields. At most 10 objects are allowed.
  • A row without label, or with label: null, renders value as one full-width line.
  • Content is always plain text. HTML, Markdown, links, nested data, field types, and style instructions are unsupported.

Detail tags: tags

  • Omit it, send null, or send [] for no tags. Otherwise send at most 8 strings in display order.
  • Each tag is trimmed and must contain 1-20 Unicode characters. Empty values, non-string values, overlong values, and duplicates after trimming reject the entire request with INVALID_REQUEST.
  • Tags are non-interactive plain text shown only in notification detail. Their order is preserved.

Recipients: audience

typeSelectorID meaning
USERSuserIdsCompany Member ID
ROLESrolesCompanyRole ID; every eligible member matching any role receives the notification.

Copy the Company Member ID from the Web Admin company member list. The member must belong to the company bound to the third-party application.

json
{
  "audience": {
    "type": "USERS",
    "userIds": ["companyMemberId"]
  }
}
json
{
  "audience": {
    "type": "ROLES",
    "roles": ["roleId"]
  }
}
  • Omit audience or send null to target every eligible active user in the bound company.
  • The matching list must contain 1-100 raw strings. IDs are trimmed; blanks are ignored; values are deduplicated and sorted as a set.
  • Nonexistent, inactive, deleted, or cross-company IDs are ignored while all remaining valid targets receive the notification.
  • A valid selector with no valid targets is accepted and completes with recipientCount=0; it never falls back to company-wide delivery.
  • The non-matching selector may be omitted or null. Other mismatches, unknown types or fields, missing or empty matching lists, non-string entries, and more than 100 entries return INVALID_REQUEST.

Limits and idempotency

  • The complete body is limited to 256 KiB. requestId, title, and body allow at most 128, 100, and 1000 Unicode characters respectively.
  • Each App ID is limited to 10 requests per minute and 100 per hour.
  • Retrying the same requestId with equivalent normalized content returns the stored result. Image, display-field, and tags order matters; audience order and duplicates do not.
  • Reusing requestId with different content returns IDEMPOTENCY_CONFLICT. A completed request can return SUCCESS, PARTIAL_SUCCESS, or FAILED instead of ACCEPTED.

Acceptance response

A valid request returns HTTP 202:

json
{
  "requestId": "order-20260819-001",
  "notificationId": "00000000-0000-0000-0000-000000000001",
  "status": "ACCEPTED"
}

ACCEPTED means Engine stored the request for dispatch. It does not prove that APNs/FCM or a device delivered the notification.

Callback results

If a Callback URL is configured, Engine sends one terminal result:

json
{
  "requestId": "order-20260819-001",
  "status": "PARTIAL_SUCCESS",
  "recipientCount": 8,
  "pushAcceptedCount": 1,
  "pushFailedCount": 1
}

recipientCount is the number of notification-center recipients. pushAcceptedCount and pushFailedCount are Push Outbox batch counts, not user counts, device counts, or proof of system-banner delivery.

Callback test

Each admin Callback test sends a distinct probe:

json
{
  "requestId": "callback-test-<unique-id>",
  "status": "SUCCESS",
  "recipientCount": 1,
  "pushAcceptedCount": 1,
  "pushFailedCount": 0,
  "test": true
}

When test is true, validate the shape and return 2xx, but do not match the probe to a submitted notification request or update business delivery state. Its counts are fixed probe values, not real recipients or Push Outbox batches.

Terminal status is SUCCESS, PARTIAL_SUCCESS, or FAILED. Return any 2xx response to acknowledge it.

Otherwise Engine retries after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, and 24 hours. Callbacks currently have no HMAC signature: require HTTPS, match the requestId of non-test results to your own request, restrict exposure where possible, and process callbacks idempotently.

Error reference

HTTPCodeMeaning
400INVALID_REQUESTContent type, JSON shape, size, or field validation failed.
401INVALID_APP_CREDENTIALSApp ID or App Secret is invalid.
403APPLICATION_INACTIVEThe application or bound company is inactive.
409IDEMPOTENCY_CONFLICTThe requestId was reused with different content.
429RATE_LIMITEDThe per-application rate limit was exceeded.
503NOTIFICATION_UNAVAILABLENotification processing is temporarily unavailable.

Security and delivery scope

  • App ID and App Secret are server-to-server credentials. Never embed them in a browser, mobile app, public repository, URL, or client-side storage. Rotate the App Secret if it is exposed.
  • Private target IDs are never written to application logs. The private audience selector and target IDs are also excluded from GraphQL, Push, Callback, realtime, and navigation payloads.
  • Without audience, every eligible active user in the bound company receives a notification-center entry. With it, only valid resolved targets do. Those recipients are included in recipientCount.
  • System Push is attempted only for devices with an active PushInstallation that permits the BUSINESS category. It still does not guarantee a system banner on every device.
  • The complete imageUrls list is available only in authorized detail. Engine may send the first URL in imageUrls to APNs/FCM for a native system banner. displayFields and tags are not included in Push or Callback and are non-interactive plain text; never include secrets, access tokens, payment data, or other sensitive values.
  • The title and body must remain complete and understandable without the images. Image download or rendering failure does not change the accepted, Callback, or Push status.
  • The mobile client accepts at most 5 MiB per image and gives the complete download operation 10 seconds. It does not follow 3xx redirects, so every URL must return the image directly.
  • The mobile client downloads images from the public CDN. Use no-auth URLs that need no cookies, keep them available for at least 90 days, and account for the CDN receiving the user's IP, request time, and User-Agent.
  • Because URL content can change, use immutable object URLs when stable historical display matters.