Customer.io Trivia

10/14/2025

Problem Statement

We want to have in-app popups for trivia games, which when answered correctly, will award a given prize to the user. In-app popups with the capacity to send customer.io messages are already implemented, so using these to generate prizes is left.

Proposed Solution

Use Customer.io webhooks to send a request to our backend when a certain event is received.

On the Customer.io side, I would configure the in-app popup to create a Customer.io event that indicates the trivia has been correctly answered. Separately, I’ll create a broadcast that gets triggered when this event is detected for a user, and will cause a webhook to be fired. This webhook will make a POST request to a route in our backend (bet_service/cio/trivia_win) which includes the user’s user_id and the item to award them in the request body.

On the backend, there will be a route for bet_service/cio/trivia_win. All “cio” routes will use a dependency that validates the response is coming from our Customer.io by checking the IP or forwarded IP is in Customer.io’s IP whitelist and verifying the value of the signature in the header as described in their docs. The route will then send a 200 response and then create the prize through sqlalchemy commands.

For extra security, this transaction will be stored in a database table called “trivia_wins” to ensure that a user doesn’t receive a prize for the same trivia multiple times. Each trivia will have a unique ID which will be set in Customer.io. This ID and the user’s user_id will be stored in a single row to indicate that this user received a prize from this trivia. I’m considering adding other fields, which I discuss in “Open Questions”

Architectural & Technical Details

A new table “trivia_wins” which would be added to backend/libs/db/models as

class TriviaWins(Base):
    __tablename__ = "trivia_wins"

    trivia_id = Column(String(64)) # Open to using a different datatype
    user_id = Column(String(64), ForeignKey("users.user_id"), nullable=False)

Next Steps

  • Set up the broadcasts on Customer.io to trigger an event via trivia responses and respond to this event with a webhook
  • Create the bet service route to validate a Customer.io webhook request and generate a prize accordingly
  • Deploy the db schema changes to our hosted environments

Action Items

  • Approve overall approach described here
  • Decide whether we want to store trivia related data in our database to determine answers to the “Open Questions” section

Open Questions

There are some other fields that I’m considering adding to the trivia_wins table, but am leaning towards excluding.

Firstly, the webhook includes an idempotency key in its request headers, which should be used to ensure that the same webhook trigger doesn’t affect our app’s state multiple times. However, the user_id already serves this function in a more extreme way since a repeated webhook request will have the same user_id and trivia_id, and the database will be checked to ensure this combination hasn’t already been added.

The second set of potential additions to the trivia_wins table revolve around data collection. It may be useful to store info like the prize that was generated for analytics purposes. It may also be useful to create a record for all trivia responses, not just correct ones, for this reason. However, since this data is coming from Customer.io, it seems likely that this information will be tracked within Customer.io anyway.

If we don’t care to store this data for analytics, then I would also consider discarding old quiz responses to save storage. This would require the trivia id to be sequential in some way, or some other field to be added to mark the age of a record.

Approvals

  • Trace Carrasco