Changelog and API stability
This page lists every change to the partner API, webhooks, events and roster.js that your integration could notice, and the rules Roster follows before making one. Check it before you upgrade your integration, and when something behaves differently from what you built against.
Versions
The contract has a semantic version, MAJOR.MINOR.PATCH. It is the info.version of the OpenAPI document behind the API Reference, and every response the API sends carries it in the Roster-Api-Version header. Responses that the gateway in front of the API sends on its own do not carry it (see API errors).
- MAJOR changes only for a breaking change, and a new major ships under a new path prefix:
/v1is major 1, and a major 2 would be served under/v2. An integration built on/v1keeps calling/v1. - MINOR changes for additive changes only, listed below.
- PATCH changes for a documentation fix, or a behavior fix that keeps the contract as it is.
A published version never changes: a version number always names the same contract. Every new version gets a dated entry on this page.
Default and LTS
- Default is the newest generally available major; today, major 1 (
/v1). It receives new minor and patch versions. These guides, the partner portal and new partners use it. - LTS is the previous major, from the day a new major becomes the Default. It receives only security patches and critical fixes, never a new minor, and it is supported for 12 months from the day it becomes LTS. Its end date is published on this page that same day.
- A new major first exists in the sandbox only, as a preview, before it becomes the Default. Roster announces the preview at least 14 days in advance, on this page and by email to the owners of your workspace.
Production access is not open yet; these rules apply there from the day it opens.
How the API changes
Additive changes ship at any time, in a new minor version. They do not break a correctly built integration, and they are listed here when they ship:
- a new operation, or a new optional request field or query parameter;
- a new field in a response, a webhook or an event payload;
- a new event type;
- a new value in a field Roster sends you, such as a status;
- a new error
typefor a situation that did not exist before.
Build your integration to tolerate them: ignore fields, enum values and event types you do not recognize, and treat an unknown status or reason as "something changed; read the resource again" instead of failing. The response, webhook and event schemas in the API Reference are open to new fields for that reason, and each enum lists the values sent today. If you generate a validator or a typed client from them, configure it to accept unknown fields and values instead of rejecting the response or the delivery.
Breaking changes ship only in a new major. A change is breaking when an integration that worked before can fail after it, for example:
- an operation, field, event type or accepted value is removed or renamed;
- a field changes type or format, or a field that was always present can now be missing or
null; - a request field or parameter becomes required, or validation becomes stricter;
- the meaning of a field, status or event changes.
How roster.js changes
roster.js is the script your page loads to open the donor flow (see Hand off a donor session), and the script a church pastes for its giving and cancel buttons. It follows the same rules as the contract, in Versions and Default and LTS above, with its own version number and one difference: how a page picks the major.
The roster.js version is not the contract version. Roster.version returns the version running on your page, each new version gets its own dated entry below, and a version number never refers to two different builds of the script. For roster.js, a change is breaking, and ships only in a new major, when something your page uses changes or goes away: Roster.open, Roster.mount and everything else on window.Roster, the callbacks and what they receive, the flow_* events, the roster:ready and roster:unavailable events, the documented data-roster-* attributes and #roster-* links, or their documented behavior. Additive changes ship in a new minor, and fixes in a new patch.
The script keeps the same address for every major: the /v1/ in /v1/roster.js does not change. Instead of a new path prefix, the data-roster-channel attribute on the script tag picks the channel. A script tag without data-roster-channel uses Default, so every snippet published so far keeps working as it is. To stay on the previous major while it is LTS, add data-roster-channel="lts":
<script src="https://flow.rostergiving.com/v1/roster.js" data-roster-channel="lts" async></script>
- While there is only one major, as today (major 1, the Default),
ltsloads the same script as Default. - When the LTS support of a major ends, a page set to
ltsloads Default. - An unknown channel name loads Default and writes a warning to the browser console. It does not stop the flow from opening.
A new major of roster.js is announced like a new major of the contract, and the announcement says how to load its preview in the sandbox.
How to read an entry
Each entry has the date it shipped, the contract version that carries it (for roster.js, its own version), the operations or events it touches, whether it is Breaking, Additive or a Fix (a behavior fix that keeps the contract's fields and events as they are), and what you need to do.
Changes
2026-10-19 · Additive · roster.js 1.2.0
A church can install roster.js on its site before Roster launches in that environment. Until launch, the script loads, tells the church's dashboard that it is installed, and shows nothing on the page. At launch, the buttons appear on every site that already has the script, with no change to the page.
- Before launch, no giving or cancel button is drawn, and a church's own button (
data-roster-open) and#roster-giveand#roster-managelinks are hidden. The church's dashboard shows the button as detected. - Before launch,
Roster.openandRoster.mountshow nothing and return acloseorunmountthat does nothing.roster:readyis still sent, andwindow.Rosteris there. - At launch,
roster.jsremoves thehiddenattribute from a church's own button (data-roster-open) and from#roster-giveand#roster-managelinks. A church's own button can carryhiddenbefore launch, so it stays hidden on a page where the script does not load. ?roster-preview=1at the end of the address of a page with the script shows the buttons before launch, only in that browser.?roster-preview=0hides them again. The donor flow they open does not take gifts before launch.- In the sandbox, Roster has launched: nothing changes there.
What you need to do: nothing. If your page opens donor sessions with Roster.open in an environment that has not launched, nothing opens until it does.
2026-10-19 · Additive · contract 1.7.0
A donor who comes back to a gift that has not started can change the amount, and you receive enrollment.updated when they do. The API, the fields and the event types are unchanged.
- A donor who leaves before finishing (for example, to find their ID) and comes back to the same giving page enters the amount again. If the gift has not started and the amount is different, the pending gift takes it, and you receive
enrollment.updatedwith the newmodeandvalue. See Review, expiry and returning donors. - In the hosted flow, a gift above 20 percent or above $3,000 per paycheck no longer asks for a new code before it is created: the donor confirms the amount with a checkbox (
agree-amber). Thereauthscreen is gone. The checkbox also appears on thepercentscreen aftergift-addorgift-move, and onfixed-insteadabove $3,000. See the screen table.
What you need to do: nothing. If your automated browser tests wait for the reauth screen, tick agree-amber instead.
2026-10-18 · Fix · contract 1.6.2
POST /v1/donor_sessions accepts a fixed amount up to $20,000 per paycheck.
- With
mode: "fixed",valueis now from 1 to 20,000 dollars, with at most two decimal places. Up to contract 1.6.1, the maximum was 4,500. A value above 20,000 receives400with thedetailfixed value must be between 1 and 20000 dollars. - Above $4,500 per paycheck, the gift is created and its transfers wait until the donor confirms the amount from an email. See Before you start.
- The donor's own screens follow the same maximums: a whole percentage up to 35, or up to $20,000 per paycheck.
mode: "pct", the responses, webhooks and events are unchanged.- In the hosted flow, an email whose domain does not receive mail (for example, a typo such as gmial.com) takes the donor back to review to check it before identity verification. This also applies to sandbox test donors: use an address on a domain that receives mail.
What you need to do: nothing. If your platform capped fixed amounts at $4,500 because of this API, you can raise that cap. If your sandbox tests type an email on a domain that does not exist, use one that receives mail.
2026-10-18 · Fix · contract 1.6.1
In the sandbox, the hosted flow of your donor sessions now shows the production screens, and the switch from a percentage to a fixed amount is announced after the donor accepts it. The API, the fields and the event types are unchanged.
- Production screens in the sandbox. A donor session (
POST /v1/donor_sessions) and a manage session (POST /v1/manage_sessions) now open the same screens as production: identity verification asks for a photo of an ID when it needs one, and the payroll is connected in the payroll provider's window, inside the flow, in the provider's test mode. The Sandbox simulation notice and the Simulate acceptance and Simulate recurring gift authorization controls are gone: the donor accepts the agreements and the recurring authorization as in production. Test numbers from+1 202-555-0100to+1 202-555-0199, the code000000and the fictional identity work as before. The/sandbox/simulate/*operations are unchanged: use them in scripts and CI that do not drive the payroll window. See Test donors and 4 Activate the enrollment. enrollment.updatedafter the new authorization. When a donor whose payroll does not take a percentage switches a gift that has not started to a fixed amount,enrollment.updated(mode: fixed,valuein dollars) is now sent after the donor accepts the new authorization for that amount, not when they choose it. A donor who stops before accepting sends no event, and the enrollment keeps the session's percentage. See When the payroll does not take a percentage.- Show the confirmed gift on
flow_completed. ReadGET /v1/donor_sessions/{id}for theenrollment_id, thenGET /v1/enrollments/{id}for themodeandvaluethe donor confirmed, including a switch to a fixed amount. See Show the gift when the flow completes. - The automated browser tests table now lists the payroll screens (
payroll-signin,payroll-confirm,payroll-installing,payroll-confirming,fixed-instead,allocation-adjust) and the ID photo controls ofin-review(docv-here,docv-text).
What you need to do: if a browser test of the sandbox flow clicked the simulated controls or waited on the connection screen for a simulator, follow the new screens, or keep calling POST /sandbox/simulate/payroll-connected once the donor reaches payroll-signin. If you recorded the fixed amount when enrollment.updated arrived, nothing changes; it now arrives only once the donor accepts it.
2026-10-16 · Additive · contract 1.6.0
POST /v1/donor_sessions has a new error type: 409 donor-sessions-not-open, in an environment where donor sessions are not open yet. Nothing is created. Once donor sessions open there, the same request with the same Idempotency-Key creates the session. The sandbox does not return it.
What you need to do: nothing, if you already handle a 409 type you don't recognize without retrying in a loop.
2026-10-16 · Fix · roster.js 1.1.1
Every screen the donor sees now has the same size, and the frame opens at that size.
- The frame that
Roster.mountand the giving button create starts 715px tall, the height of the screens, instead of 560px. This applies to the donor flow and to changing or canceling a gift. The first screen no longer appears cut off while it loads. - On a phone (screens up to 460px wide), the window that the giving button and
Roster.openshow fills the screen, and so does the donor's card. window.Roster, the callbacks and theflow_*events are unchanged.
What you need to do: nothing. If you call Roster.mount inside a container with a fixed height, make it at least 715px tall.
2026-10-15 · Breaking · contract 1.5.0
POST /v1/donor_sessions takes only a whole percentage.
- With
mode: "pct",valueis a whole number from 1 to 35, such as12. A value with a fraction, such as12.5, now receives400with the samedetailas a value outside that range:value for mode "pct" must be a whole percentage from 1 to 35, like 12 (not 12.5). Up to contract 1.4.1, steps of 0.5, such as7.5, were accepted. See Before you start. mode: "fixed",funds[].percent, the responses, webhooks and events are unchanged.- Stricter validation is a breaking change under the rules above, which keep those for a new major. Roster makes this one in a minor because production access is not open yet, so that no integration is built on fractions of a percent.
What you need to do: if your platform lets a donor pick a fraction of a percent, offer whole percentages only. A request that worked with 7.5 now receives 400.
2026-10-13 · Fix · contract 1.4.1
The documentation of POST /v1/refunds/{id}/cancel now says when a refund that is still requires_action can no longer be canceled. The API behaves as before.
- A refund of part of a donation the church was not paid yet can be canceled only before Roster's review. After it, the rest of the donation is on its way to the church, and the request gets
409refund-not-cancelable, even while the refund isrequires_action. Before this version, the documentation said such a refund could be canceled until Roster approved it for sending. - In the sandbox that review comes right after the refund is created: to try canceling, refund a donation the church was already paid. See Cancel a refund.
- The
detailofrefund-not-cancelablenow names the three cases.
What you need to do: nothing, if you handle 409 refund-not-cancelable by reading the refund again.
2026-10-13 · Fix · contract 1.4.0
In the sandbox, donations that came through you reach the church, as contract 1.2.0 announced.
- Until now, a sandbox donation that came through you stayed pending, because no fee agreement with you was in effect on its deposit date. Every sandbox workspace now has a test fee agreement from the day it was created: Roster's standard 2% fee and a 0% commission for you. The
feeleg of these donations carries Roster's fee only, and thechurchleg the rest. See Approval before bank instruction. - A donation pending for this reason and deposited within the last 30 days is picked up by a following weekday cutoff, with the same checks as any other donation. For an older test deposit, simulate a new one.
- Production is unchanged: your fees come from your signed agreement.
What you need to do: nothing.
2026-10-09 · Additive · contract 1.4.0
Refunds: you can return a donation that came through you to the donor, in full or in part, and follow it to the end.
POST /v1/refundscreates a refund:donation_id, an optionalamount_minor(without it, what is left of the donation),reason(duplicate,requested_by_donororother), the requiredcancel_recurrenceand optionalmetadata. It requiresIdempotency-Keyand, like the other creations, the key keeps its body for as long as the refund exists. More than what is left gets422amount-exceeds-refundable; a donation being sent to the church's bank gets409donation-in-flight. A refund of a donation the church was already paid does not wait for the church: Roster sends the money to the donor and deducts the refund amount from the church's next payouts. See refunds.GET /v1/refunds/{id},GET /v1/refunds?donation_id=…andPOST /v1/refunds/{id}/cancelread, list and cancel refunds. A refund approved for sending, or already ended, gets409refund-not-cancelable. A donation or refund that is not yours gets404.- New
Refundobject:status(pending,requires_action,succeeded,failed,canceled),pending_reason(in_review,awaiting_approval,processing),reason,failure_reason(not_refundable,bank_error), andfunds, the part taken from each fund of a split donation. - New events
refund.createdandrefund.updated, with the whole refund, on webhooks and inGET /v1/financial_events. Subscribe your endpoint to them to receive them. donation.updatedcarriesrefunded_minor, in total and for each fund infunds, counting only refunds that succeeded.statusreadsreversedwhen the whole donation was refunded, andin_reviewwhile a refund of a donation the church was not paid yet waits for Roster's review.- Sandbox:
POST /sandbox/simulate/refund-returnedplays the donor's bank sending a refund back, andGET /sandbox/fixturesreturnstest_refund_destination, the test bank login a test donor uses to add the account a refund goes to. - Documentation only: the
simulatedfield of the identity simulator now says plainly that it is the code Roster's identity check received for theoutcomeyou sent.
Refunds are available where financial integration is enabled: today the sandbox; in production the refund operations answer 503.
What you need to do: nothing, if your receiver ignores fields, statuses and event types it does not recognize. To offer refunds, subscribe your endpoint to refund.created and refund.updated, and read refunded_minor on donation.updated. If you treated reversed as reserved, handle it now as "refunded in full".
2026-10-09 · Fix · contract 1.3.1
Each platform now receives a donor's contact details as the donor gave them through that platform.
- The donor profile in
donation.updated,donor.updated,GET /v1/donor_sessions/{id}and the partner portal is the one the donor gave in donor sessions you created. If the donor also gives through another platform and changes their email there, or changes it directly with Roster, you keep the email they gave you, and itsupdated_atdoes not change. Before this version, every platform received the latest profile, including the time of a change made elsewhere, which told one platform when the donor had used another. donor.updatedreaches you only for a contact change the donor makes in a donor session you created, for each church where you already received that donor's donations.- For a donor whose details were recorded before this version, you receive the latest profile, as before, until they next give their details in a donor session you created.
- Event and response fields,
revisionand both feeds are unchanged. See Use donor data in your existing integration.
What you need to do: nothing. Keep upserting the person by donor ID with the newest profile timestamp. Events already delivered stay in your financial journal.
2026-10-09 · Additive and fix · contract 1.3.0
Every event says which environment it comes from, each webhook delivery names its endpoint, a retry reaches only the endpoints that have not accepted the event, the deposit simulator tells you when the money can move, a save of the donor-flow settings tells you what it removed, and a few behaviors return to what this contract already promised.
- Every event envelope carries
livemode:truewhen the event comes from production (sk_live_keys),falsewhen it comes from the sandbox (sk_test_keys). It is on every event inGET /v1/eventsandGET /v1/financial_events, on every webhook delivery, and on thewebhook.testdelivery. Events recorded before this change carry it too. - If the same receiver gets events from both environments, use
livemodeto keep them apart. See The envelope. POST /sandbox/simulate/depositreturnseligible_atandnext_run_atin its202: when the donation's 120-minute processing window ends, and the earliest weekday 4:00 p.m. America/New_York settlement run that can pick it up. That run still checks the enrollment and the church. See 9 Deposit.POST /v1/donor_sessionstreats a blankdonor.date_of_birthas absent again, as it did up to contract 1.1.0. The400that contract 1.2.0 introduced for it was stricter validation, which the rules above count as breaking, so it is withdrawn. The schema now states that an empty or blank value is accepted.POST /sandbox/simulate/kycrefuses with400a second final outcome sent while the first one is still being processed, as the operation already stated. Before, the second outcome went through and sent a donor who had passed back into review.POST /v1/churchesand the import answer anamethat is not a string with "name must be a string" instead of "name is required".- A browser preflight (
OPTIONS) from a site outside the allowed list no longer carriesRoster-Api-Version, as API errors already described. - Documentation only: the
simulatedfield of the identity simulator is a string, while the bank simulators return an object under the same name; which simulators answer400when repeated; and which request valuesdetailmay name. PUT /v1/flow-settingsreturnsremoved: the properties that were saved before the request and are not saved after it, named by their path in the request body, such asdisplay_nameorcustomization.css.padding. It is empty when the save removed nothing. See Concurrent saves.GET /v1/flow-settingsreturnedversion0for settings saved before versions were counted, so a save withexpected_version0replaced them instead of receiving409. Those settings now read1;0means nothing has been saved.- Every delivery, the test one included, carries the
Roster-Webhook-Endpointheader with thewe_id of the endpoint it was sent to. The signature does not cover headers: use the id to pick the secret, and trust it once the signature verifies. See Verifying the signature. - A retry goes only to the endpoints that have not accepted the event yet. Before, when one endpoint failed, every endpoint subscribed to the event received it again at each retry, so a dead endpoint multiplied deliveries to the healthy ones. See Delivery semantics.
- Fix:
health.statusinGET /v1/webhook_endpoints/{id}/deliveriesalso counts the failures among the last 50 attempts, besides the failures in a row it already looked at:brokenwhen 25 or more of them failed,degradedwhen 5 or more did. Before, an endpoint failing every other delivery readhealthy. The values and the other fields are unchanged, and no endpoint reads healthier than it did before. - In the sandbox, the weekday cutoff instructs an eligible transfer by itself: nothing waits for an approval from Roster, so certification step 10 can be finished without asking Roster. Production is unchanged. See 10 Returned transfer.
What you need to do: nothing, if your receiver ignores fields and headers it does not recognize. To keep sandbox events out of production records in a receiver shared by both environments, read livemode. If you kept a version of 0 for donor-flow settings that were already there, read them again before your next save; if removed lists a property you meant to keep, save again with it. If you alert on health.status, expect it to fire for an endpoint that fails intermittently. Keep deduplicating by event_id: a duplicate delivery is rarer, not impossible. A request with a blank donor.date_of_birth that received 400 under 1.2.0 now succeeds, without a date.
2026-10-09 · Additive · contract 1.2.0
A gift can be split among funds of the same church, each donation reports the amount of each fund, and a church can receive its payouts weekly.
POST /v1/donor_sessionsacceptsfunds: your fund identifier, an optional name and a percent, adding up to exactly 100, or, for a fixed gift, an amount in dollars, adding up tovalue. Sendfundorfunds, not both. See Split a gift among funds.- The donor session,
GET /v1/enrollments/{id}and theenrollment.createdevent returnfundsas you sent them. donation.updatedcarriesfundswith each fund'samount_minor, adding up exactly to the donation. See Split among funds.- A church can receive its payouts once a week.
POST /v1/churchesand the import acceptpayout_schedule(daily, the default, orweekly), the newPOST /v1/churches/{id}/payout-schedulechanges it, andGET /v1/churches/{id}returns it. See When the church receives its payouts. - A blank
donor.date_of_birthnow receives a400, as the field'sdateformat already stated. Before, it was treated as absent. Withdrawn in contract 1.3.0, above: it was a breaking change. - In the sandbox, donations that came through you now reach the church: the
feeleg carries Roster's fee and your commission together, and thechurchleg the rest. In production they stay pending as before. See Approval before bank instruction.
What you need to do: nothing. Without funds or payout_schedule, requests and payloads are unchanged, and every church keeps receiving daily.
2026-10-04 · Additive · roster.js 1.1.0
roster.js gets its own version number and the release channel attribute, as described in How roster.js changes above. roster.js 1.0.0 is the script as published with the baseline below.
data-roster-channelon theroster.jsscript tag selects the release channel. Without it, the page uses Default.Roster.versionreturns the version ofroster.jsrunning on the page.
What you need to do: nothing. Snippets without data-roster-channel load what they loaded before.
2026-10-04 · Baseline · contract 1.1.0
This is the first stable baseline of the sandbox contract that partners integrate against: Partner API v1, contract version 1.1.0, with the API Reference, the event catalog and these guides as published on 2026-10-04. The OpenAPI document of this version, served at https://docs.rostergiving.com/openapi.json, has SHA-256 34ecba737cfea6e0b117b62cb1fe358b044a27d3d800c1554b87302bdea82ca5. Earlier publications marked 1.0.0 were pre-release and are superseded by this baseline. Changes made before it are part of the baseline and are not listed one by one, except this one, which an early reader may have seen:
enrollment.pausedno longer listsallocation_above_authorizedas areason. When Roster pauses giving on its side, the event carries noreason: show the gift as paused and in review, without guessing why. This is part of the baseline, not a breaking change after it.