Notifications

Overview

Kard sends near real-time webhooks about important events related to transactions in your rewards program. They come in two kinds, and the difference determines what you do with them.

Reward events (earnedRewardApproved and earnedRewardSettled) are built to be passed as push notifications straight through to your users. Each carries suggested notification text and an attribution link, so a matched transaction can become a push notification without any additional lookups.

earnedRewardRejected is not intended to be shown to your users. It reports that a transaction which initially matched has been rejected and will not result in a reward — a signal for your system to act on internally, not something to forward as a push notification. That’s why the payload carries a reason instead of an attribution link.

Each event type is delivered to the webhook URL you designate for it. You subscribe to events yourself via the Create Subscriptions endpoint. Every delivery is HMAC-signed so you can confirm it came from Kard.

How it works

  1. Subscribe. Call Create Subscriptions with the event type you want and the URL it should be delivered to.
  2. Receive. When a qualifying event occurs, Kard POSTs the notification to that URL with a notify-signature header.
  3. Verify. Recompute the HMAC over the body with your webhook key and compare it to the header. Reject anything that doesn’t match.
  4. Respond. Return a 2xx. Use the payload’s id as an idempotency key so a redelivery never notifies a user twice.

Event types

EventSent whenBest practices
earnedRewardApprovedAn approved transaction qualifies for a reward.Notify the user. Carries message text and an attribution link.
earnedRewardSettledA settled transaction qualifies for a reward.Notify the user. Carries message text and an attribution link.
earnedRewardRejectedA transaction that initially matched is later rejected and does not result in a reward.Reconcile internally. No attribution link; carries a reason instead.

Full payload schemas for each event live in the Notifications Webhook API reference.

What’s in a payload

All three events share a core: a unique id on the envelope, plus message, transactionId, and transactionAmountInCents in attributes (transactionTimestamp is optional). Use id as an idempotency key so a redelivery never double-notifies a user or double-reverses a reward, and transactionId to tie the event back to the transaction you submitted.

What differs is what surrounds that core — and, critically, whether message is fit to show a user.

Reward events: earnedRewardApproved, earnedRewardSettled

These are the payloads designed for pass-through to your users.

  • message: user-ready copy. Send it as a push notification as-is.
  • attributionUrl: tracks your user’s interactions with the notification. See the Attributions guide for how to use it.
  • name: the merchant name.
  • Offer context: categoryName, userReward, purchaseChannel, surveyUrl, and assets (merchant images whose URLs are signed for attribution tracking, to be loaded as-is). earnedRewardSettled also carries commissionEarned.

attributes.message can be served directly as a push notification:

earnedRewardApproved push notification

earnedRewardApproved: the reward is pending, so the message names no amount.

earnedRewardSettled push notification

earnedRewardSettled: the reward is final, so the message states the amount.

Rejections: earnedRewardRejected

  • reason: why the transaction was rejected. Values below.
  • message: every rejection carries the same string — Your transaction did not result in a reward.
  • No attributionUrl.
reasonMeaning
AGGREGATOR_CARD_OVERLAPThe card is already linked to another user at Kard, so the transaction was rejected at match time.
SETTLEMENT_REJECTEDThe transaction was approved at match time but rejected at settlement.
USER_NOT_ENROLLEDThe user is not enrolled in cardlinked rewards.
USER_NOT_IN_AUDIENCE_SEGMENTThe user is not part of the audience segment eligible for the offer.

The reason values are listed above. Kard may add new values over time, so handle any unrecognized value as a generic rejection rather than failing the request.

Verifying the signature

Notifications are outbound POSTs to a URL you provide, authenticated with an HMAC signature rather than a shared credential on your side.

You’ll be issued a webhook key. Kard uses it to compute an HMAC of the webhook body and sends the result in the notify-signature header. To validate a delivery, compute the same HMAC yourself — your key, the request body, SHA-256 — and compare it to the header value. Header names are matched case-insensitively.

Code recipe: verify, then ingest

A minimal Node.js service that verifies the signature in middleware and processes the payload in the route.

auth.js — signature verification middleware

1const { createHmac } = require("crypto");
2const secretKey = issuer_webhook_key; //provided in postman_environment.json
3
4const verifyToken = (req, res, next) => {
5 // grab HMAC signature from Notify-signature header of request
6 const token = req.get("notify-signature");
7
8 if (!token) {
9 return res.status(403).send("A token is required for authentication");
10 }
11
12 try {
13 // cast webhook as string
14 const stringRequest = JSON.stringify(req.body);
15
16 // hash using sha256, webhook key, and webhook body as string
17 const hash = createHmac('sha256', secretKey)
18 .update(stringRequest)
19 .digest('base64')
20
21 // verified request
22 if (token === hash){
23 return next();
24 }
25
26 // unverified request
27 return res.status(401).send("Invalid Token");
28 } catch (err) {
29 return res.status(400).send("Bad Request");
30 }
31};
32
33module.exports = verifyToken;

index.js — the POST endpoint

1const express = require('express');
2const auth = require('./auth');
3
4const port = 3000;
5const app = express();
6
7app.use(express.json());
8
9app.post('/notifications-webhook', auth, (req, res) => {
10 try {
11 // insert code that processes the webhook
12 console.log('notification webhook: ', req.body);
13 }
14 catch (err) {
15 res.send(err);
16 }
17 res.status(200).send('Thanks Kard!');
18});
19
20app.listen(port, () => {
21 console.log(`Example app listening on port ${port}`);
22});

Testing your integration (Sandbox environment only)

Sandbox environment only. The trigger endpoint described here exists only in Kard’s test (sandbox) environment and is not available in production.

Once your endpoint verifies signatures, you can confirm it handles earned-reward webhooks end to end without waiting on a real matched transaction. The Simulate Test Notification endpoint sends a simulated earnedRewardApproved or earnedRewardSettled webhook to the URL you subscribed. You supply the userId and transactionId to include in the simulated transaction; offer information is sourced from an existing sandbox offer, so the payload matches the shape of a production notification and is HMAC-signed like any other delivery.

Prerequisite: an enabled subscription for the event you want to trigger (see Create Subscriptions). If there isn’t one, the call returns 409.

$curl -X POST "https://<test-api-host>/v2/issuers/{organizationId}/notifications" \
> -H "Authorization: Bearer <token>" \
> -H "Content-Type: application/json" \
> -d '{
> "data": [
> {
> "type": "simulateTestTransaction",
> "attributes": {
> "userId": "issuer-user-123",
> "transactionId": "network-txn-9f8e7d6c",
> "eventName": "earnedRewardSettled"
> }
> }
> ]
> }'

A successful call returns 202 with the generated eventId. The notification is delivered to your webhook and can be listed and replayed like any other. Each call generates a new eventId, so you can trigger as many test notifications as you need (subject to a per-issuer rate limit).