Skip to content

Registration Platform

A registration platform integration captures donations made during club registrations and forwards them to ASF. Every request from this channel carries registration-specific identifiers in sourceMetadata.

  1. The registration platform calls GET /accounts to validate that the club has activated donations on that platform and obtain the details of the fundraising cause
  2. The fundraising cause is displayed to the club member during the registration flow
  3. The club member completes a registration with an attached donation.
  4. The integration job polls (or receives a webhook for) settled registration donations.
  5. For each donation, validate the fundraiser then POST /donations.
  6. When the bank settles, POST /remittances keyed by bankReferenceId.

A Registration Platform channel populates these:

FieldExampleRequired
registrationIdRegistration Platform registration idNo
externalIdRegistration Platform Unique Member IDNo

Add any further registration-side identifiers (seasonId, productId, memberId) — sourceMetadata accepts arbitrary properties and ASF stores them verbatim.

The GET /accounts endpoint both validates the activation and returns the details of the fundraising cause for display in your UI.

/accounts/ASF123456?channel=XYZ%20Registration%20Platform
GET /accounts response
{
"apiVersion": "1.0.21",
"correlationId": "3ea4aba4-d474-4208-b814-fa35e1ef2eb3",
"method": "/accounts/ASF123456",
"data": {
"items": {
"name": "Wombat Park Softball Club",
"memberNumber": "ASF123456",
"abn": "9876543210",
"products": [
{
"name": "XYZ Registration Platform",
"description": "Your support will help our club upgrade our digital scoreboard.",
"status": "Activated",
"logo": "https://assets.asf.org.au/images/logo.png",
"coverImage": "https://assets.asf.org.au/images/cover_image.png",
"donationAmounts": [10, 20, 50, 100],
"story": ""
}
]
}
}
}
POST /donations body
{
"source": {
"channel": "XYZ Registration Platform",
"fundraiserId": "ASF123456"
},
"donor": {
"email": "jenny@example.com",
"firstName": "Jenny",
"lastName": "Smith",
"billingAddress": {
"postalCode": "2604",
"country": "Australia"
},
"verifiedEmail": "jenny@example.com"
},
"transaction": {
"id": "504789900",
"date": "2026-05-11T11:00:00+10:00",
"amount": "50"
},
"sourceMetadata": {
"registrationId": "954uOVtyJz7K"
}
}

Payment splitting and settlement at point of transaction is preferred to ensure that donors receive their donation receipt immediately. If payment splitting is not feasible, partners have scheduled a job that pages settled registrations and forwards donations. The reference implementation below is a Node.js worker; adapt the trigger to whatever job runner you use (Cloudflare Cron Trigger, Vercel Cron, AWS EventBridge, a long-running container, etc.).

worker/forward-donations.js
import { randomUUID } from 'node:crypto';
import { fetchSettledRegistrations } from './registration-platform.js';
import { db } from './db.js';
const BASE = process.env.ASF_BASE_URL;
function asfHeaders(correlationId) {
return {
client_id: process.env.ASF_CLIENT_ID,
client_secret: process.env.ASF_CLIENT_SECRET,
'X-Correlation-ID': correlationId,
'Content-Type': 'application/json',
};
}
export async function forwardSettledDonations() {
const since = await db.lastSettledAt();
const registrations = await fetchSettledRegistrations({ since });
for (const reg of registrations) {
if (!reg.donation || reg.donation.amount <= 0) continue;
if (await db.isAlreadyForwarded(reg.id)) continue;
const correlationId = randomUUID();
const fundraiserId = reg.donation.fundraiserId;
// 1. Validate
const v = await fetch(
`${BASE}/fundraisers?channel=XYZ%20Registration%20Platform&id=${fundraiserId}`,
{ headers: asfHeaders(correlationId) },
);
if (!v.ok) {
await db.recordFailure(reg.id, correlationId, await v.text());
continue;
}
// 2. Post the donation
const body = {
source: {
channel: 'XYZ Registration Platform',
fundraiserId,
},
donor: {
email: reg.member.email,
firstName: reg.member.firstName,
lastName: reg.member.lastName,
billingAddress: reg.member.address,
verifiedEmail: reg.member.emailVerified ? reg.member.email : undefined,
},
transaction: {
id: reg.donation.transactionId,
date: reg.donation.settledAt,
amount: String(reg.donation.amount),
},
sourceMetadata: {
registrationId: reg.id,
externalId: reg.donation.externalId,
seasonId: reg.season.id,
productId: reg.productId,
},
};
const d = await fetch(`${BASE}/donations`, {
method: 'POST',
headers: asfHeaders(correlationId),
body: JSON.stringify(body),
});
if (!d.ok) {
await db.recordFailure(reg.id, correlationId, await d.text());
continue;
}
const { data } = await d.json();
await db.recordSuccess(reg.id, data.items.donationReferenceId, correlationId);
}
}

Your payment processor may settle to ASF’s bank in batches. Group all donation references covered by a single bank transaction into one remittance entry:

POST /remittances body
{
"source": {
"channel": "XYZ Registration Platform",
"fundraiserId": "ASF123456"
},
"remittances": [
{
"bankReferenceId": "NAB-20260511-0001",
"amount": 1500.00,
"donationIds": [
"0000468-000788",
"0000468-000791",
"0000468-000795"
]
},
{
"bankReferenceId": "NAB-20260511-0002",
"amount": 500.00,
"donationIds": ["0000468-000798"]
}
]
}
  • Donor email is often the registrant. When a parent registers a child, the donation receipt should go to the parent. Most partners send the registrant’s email rather than the child’s. Verify this matches your fundraiser-side expectations.
  • Donations are non-refundable. As per ASF policy. If registration fees are refunded, the refund amount should exclude any donation amounts.