star

Article edited by: OOOPEN Lab | 2026-09-15

Set Up Brand Activity Data Delivery

Set Up Brand Activity Data Delivery

Activity data delivery sends activity from your selected projects to your CRM, membership system, or redemption service. You do not need a member data space to create one — whether you have one only decides which events you can receive. Each delivery connection has a dedicated signing secret and never uses your account API key.

Activity data delivery is currently available to beta accounts only. If your account is not in the beta yet, keep using Webhooks, and contact us if you would like to join.

Before you begin

  • Prepare a public HTTPS port 443 endpoint. It must not redirect or resolve to a private network.
  • Make sure your receiver can persist eventId and apply deduplication and the business update in one database transaction.
  • Only want Answer completed events? You do not need a member data space — skip straight to creating a connection below.
  • Want Mission completed, Points awarded, or Platform redemption, or want the same player recognized across quizzes? Create a member data space and a member sign-in connection first, and enable that sign-in on at least one project.

Create an activity delivery

  1. Open Account settings, then Developer.
  2. Under Activity data delivery, choose which identity this connection uses: a member data space, or No member identity.
  3. Enter a name and the HTTPS port 443 endpoint.
  4. Select New format (recommended) or Existing webhook format. The format cannot be changed after creation; create another connection to use another format.
  5. Select events and the projects allowed to send them. With No member identity selected, only Answer completed actually fires — the other three events require a member data space.
  6. Create the connection and save its signing secret immediately. It is shown only once.
  7. Send a test event and confirm the Delivered status. HTTP 200 confirms transport only, not coupon issuance, tagging, points, or another business action.

The four available events are Answer completed, Mission completed, Points awarded, and Platform redemption. Started is not available. With No member identity selected, each event's identity is an anonymous code derived from that one answer, so two submissions from the same person are counted as two unrelated events and cannot be recognized as the same person across quizzes. Recognizing the same person across quizzes requires the member identity a member data space provides.

Maggie runs a beauty-brand quiz and has not set up a member data space yet: she picks No member identity, enables only Answer completed, and uses answerId to segment players by their result. Kevin runs a two-stage loyalty campaign and needs Mission completed and Points awarded: he creates a member data space first and picks it as this connection's identity, so his receiver can record completion proof and the points actually posted, separately. Daniel runs a members-day coupon flow and enables Answer completed and Platform redemption: he also needs a member data space, deduplicates by eventId first, and lets his own membership system decide whether to issue a coupon.

New format body and signature

The New format sends the complete authoritative event:

{
  "brandId": "brand_123",
  "campaignId": "campaign_123",
  "eventId": "64-character-sha256-event-id",
  "externalUserId": "brand:v2:canonical-member-id",
  "occurredAt": "2026-09-15T08:00:00.000Z",
  "payload": { "answerId": "answer_123" },
  "schemaVersion": 2,
  "source": {
    "firebaseProject": "ooopen-project",
    "path": "answers/answer_123",
    "id": "answer_123"
  },
  "sourceProjectId": "quiz_123",
  "type": "finish",
  "webhookConnectionId": "connection_123"
}

With No member identity selected, brandId is always the reserved value __no-brand__, campaignId is always __no-campaign__, and externalUserId is an anonymous code derived from answerId (anon:v1:...). None of the three is a real brand, campaign, or member record — treat them as not applicable.

Read the unparsed request body. Concatenate X-OOOPEN-Webhook-Timestamp, one period, and the complete raw body bytes. Calculate HMAC-SHA256 with the connection's signing secret and encode it as base64url. Reject timestamps older than five minutes. Compare the result with X-OOOPEN-Webhook-Signature, then verify that X-OOOPEN-Webhook-Event-Id equals the body eventId.

const signedBytes = Buffer.concat([
  Buffer.from(timestamp + '.', 'utf8'),
  rawBody
]);
const expected = crypto
  .createHmac('sha256', signingSecret)
  .update(signedBytes)
  .digest('base64url');

What is in the finish payload

payload only carries answer-record fields that already existed at the moment the answer finished. It never reads the quiz's current content — no quiz name, no result label, no question or choice text. That's because this body is signed and can be resent: if a resend read "today's" quiz content instead, an old event replayed after the creator edited the quiz would report content that never actually happened at that time. To show human-readable names, look them up yourself using quizId and result against the Creator Data API at /api/v1/quizzes/{quizId}/answers/{answerId}.

  • answerId: this answer's ID. Always present.
  • quizId: which project this answer belongs to. Always present.
  • startedAt: when the participant started answering, not when they finished. Present only when set.
  • ref: the referrer code recorded on the answer. Present only when set.
  • result: the result identifier, never a label. Present only when set.
  • answers: the submitted answers, exactly as stored. Present only when set.
  • answersTruncated: true when the content was dropped for size. Never appears together with answers.
  • utm: attribution data, present only when your account has the paid UTM export feature.

No contact fields are ever included — no email, and utm.code (the redeem code) is withheld.

Existing webhook format body and signature

Choose Existing webhook format only when your receiver requires a data, type, and timestamp envelope. It still uses the new connection's dedicated secret. OOOPEN Lab does not call the old webhook or invent old card, member, or balance fields. The reserved-identity rules are the same as the New format: with No member identity selected, brandId and campaignId are fixed to __no-brand__ and __no-campaign__.

{
  "data": {
    "wireSchema": "ooopen-brand-activity-webhook-legacy-envelope/1",
    "eventId": "64-character-sha256-event-id",
    "type": "finish",
    "occurredAt": "2026-09-15T08:00:00.000Z",
    "brandId": "brand_123",
    "sourceProjectId": "quiz_123",
    "campaignId": "campaign_123",
    "webhookConnectionId": "connection_123",
    "externalUserId": "brand:v2:canonical-member-id",
    "payload": { "answerId": "answer_123" }
  },
  "type": "finish",
  "timestamp": "2026-09-15T08:00:00.000Z"
}

Calculate HMAC-SHA256 over the exact serialized data JSON bytes, encode it as lowercase hex, and compare it with x-ooopenlab-signature. After signature verification, require the outer type to equal data.type and timestamp to equal data.occurredAt. Finally, validate wireSchema, the event type, and its typed payload. Do not run the New format verifier on this body, or this verifier on a New format body.

Deduplicate before applying the business action

  1. Verify the signature and format.
  2. Look up eventId inside a database transaction.
  3. If it was processed, return HTTP 200 without issuing a coupon or adding points again.
  4. Otherwise, persist eventId and the business result in the same transaction.
  5. Return HTTP 200 only after the transaction commits.

Automatic retry and manual resend preserve the first body, eventId, deliveryId, and format. Delivery unknown means that the receiver may already have processed the event; check eventId before resending. Delivery records can be replayed only during their retention window.

Rotate or disable a connection

Rotating the signing secret revokes the old secret immediately. Update your receiver and send another test with the new secret. A disabled connection cannot send tests, deliver events, or resend a delivery.

Move from an existing webhook

An existing webhook is account-wide by default: it fires for every quiz under your account unless you point it at one specific quiz. Activity data delivery requires you to explicitly select the projects it can send from when you create it, up to 50 per connection. An existing webhook has a Started event; activity data delivery does not offer it at all — moving over drops it outright, with no replacement event.

The signing key changed — use a new endpoint during cutover

An existing webhook signs with your account API key. When an activity delivery connection uses Existing webhook format, the header name is still x-ooopenlab-signature, but the signing key is that connection's own signing secret, not your account API key. If your receiver keeps verifying that header with the account API key, every delivery from the new connection fails signature verification. And if both the old webhook and the new connection post to the same endpoint during cutover while your receiver only trusts one key, one side is guaranteed to fail.

If you must handle both on one endpoint, you can tell them apart from the body: a new Existing-webhook-format delivery always has wireSchema and eventId inside data; the old webhook's payload has neither field. The simpler and safer option is to give the new activity delivery connection its own new endpoint URL during cutover instead of reusing the old webhook's URL — each side then verifies its own key, and your receiver never has to decide "is this old or new" in the same code path. Delete the old webhook setting under Existing webhook (management only) only after you confirm the new connection works.

Choosing New format avoids this entirely: it always sends three distinct headers (X-OOOPEN-Webhook-Timestamp, X-OOOPEN-Webhook-Event-Id, X-OOOPEN-Webhook-Signature), the signature covers the timestamp concatenated with the raw body, timestamps older than five minutes are rejected, and none of this overlaps with the old webhook's header.

Cutover steps

  1. Add the new signature verifier and transactional eventId deduplication to the receiver. If old deliveries can overlap, also deduplicate by answerId.
  2. Re-select the identity (member data space or No member identity), projects, events, and format in Activity data delivery.
  3. Save the new secret and pass the test delivery.
  4. Enable the new connection first and monitor receipts.
  5. Delete the old setting only after the new flow works.

The cutover cannot guarantee zero duplicate events. Complete receiver deduplication before stopping the old setting.


You might also like…

  • How to use the Bracket Game module
    Check it out
  • How to use the Mission Event Page module
    Check it out
  • How to Connect Multiple Projects: Links, Project Chaining, Access Rules, and Missions
    Check it out

Make Your Brand Communication Fun

HomeAbout OOOPEN LabAboutTerms of Service and Privacy PolicyExternal Transmission DisclosuresReport Abuse

Copyright © 2026 OOOPEN Lab