6. End-to-End Scenarios
6. End-to-End Scenarios
End-to-End Scenarios
WorkedUse examplesthese coveringtutorials thewhen mostyou common partner integrations. Each scenario shows API calls in order, the data flowing between them, and which webhookswant to expect.build a real integration path from start to finish.
Scenario 11: —Add a donation button
You will create a donation checkout link from the browser using a publishable key.
What you need
pk_test_....
A nonprofit slug, such as american-red-cross.
A page or component where the user clicks Donate.
Step 1: Create the donation link
const response = await fetch('https://devapi.sirgiving.org/v1/partner/donations/create-link', {
method: 'POST',
headers: {
'X-Partner-Key': 'pk_test_...',
'Content-Type': 'application/json',
},
body: JSON.stringify({
nonprofitSlug: 'american-red-cross',
amount: 2500,
currency: 'USD',
frequency: 'once',
provider: 'stripe',
donorEmail: 'jane@example.com',
firstName: 'Jane',
lastName: 'Doe'
}),
});
const { donationLink, partnerDonationId } = await response.json();
window.location.href = donationLink;
Step 2: Store the donation ID
Store partnerDonationId in your session or database. You use it later to show status.
Step 3: Check status
GET /v1/partner/donations/status/:partnerDonationId
X-Partner-Key: pk_test_...
What you built
You now have a browser-based donation flow. The user clicks your button, SIR Giving creates a checkout link, and you can poll for reward status or listen for webhook events.
Scenario 2: Reward a user for an action
You will submit a backend action when something valuable happens in your customersystem, makessuch as a purchase
Goal: A retail customer of yours buys a product. You want to credit them with SIR tokens proportional to their spend (a "donate-with-purchase" or loyaltycompleted pattern).volunteer shift.
FlowWhat you need
[Customersk_test_....
Step 1: Decide your idempotency key
Use a key that maps to one real-world event:
purchase_98765
volunteer_shift_abc123
referral_signup_555
If you retry the same action, reuse the same key.
RequestStep 2: Submit the action
POST /v1/partner/actions/submit
X-Partner-Key: sk_live_.sk_test_...
X-Timestamp: 1746876000<timestamp>
X-Signature: <hmacsignature>
Content-Type: application/json
{
"idempotencyKey": "order_98765"purchase_98765",
"actionType": "PURCHASE",
"amount": 49.99,
"currency": "USD",
"stakeholders": [
{
"stakeholderTypeCode": "CUSTOMER",
"partnerUserId": "user_42",
"userEmail": "customer@example.com",
"userFirstName": "Jane",
"userLastName": "Doe"
}
],
"autoCreateUsers": true,
"metadata": {
"orderId": "98765",
"sku": "WIDGET-001",
"channel": "web"
}
}
ResponseStep (200)3: Store the action ID
Response:
{
"actionId": "65f1...",
"idempotencyKey": "order_98765"purchase_98765",
"status": "COMPLETED",
"tokensDistributed": 50,
"transactionIds": ["65f2..."]
}
What to do next
Store actionId againstwith your orderorder. soYou need it for refunds or support.
What you canbuilt
You reversenow it onhave a refund.
action.completedYour Failure modes
402 INSUFFICIENT_POOLPOST /v1/partner/token-pools/request409 IDEMPOTENTidempotencyKey429Retry-Afterresult.
Scenario 2 — Customer-initiated donation widget
Goal: Show a "Donate to Red Cross" button on your site. Clicking redirects to a hosted Stripe / Every.org checkout. After completion, the donor earns SIR tokens automatically.
Flow
[Browser] [Your backend] [SIR API]
│ widget mounts with pk_live_... │ │
│ GET /v1/partner/config ─────────────────────────────────────────────────────▶│
│ ◀─────────── feature flags / tip config ───────────────────────────────────────────│
│ user clicks "Donate $25 to Red Cross" │ │
│ POST /v1/partner/donations/create-link ─────────────────────────────────▶│
│ ◀───── { donationLink, partnerDonationId } ────────────────────────────────────────│
│ window.location = donationLink │ │
│ [Stripe / Every.org checkout] │ │
│ …user pays… │ │
│ (Stripe webhook → SIR; SIR processes │
│ donation, distributes tokens) │
│ │ │
│ [Optional poll] │ │
│ GET /v1/partner/donations/status/:partnerDonationId────────────────────────────────▶│
│ ◀── { status: "COMPLETED", tokensDistributed: 175 } ──────────────────────────────│
│ │ │
│ POST your webhook URL ──────── action.completed ────────│
Browser code
const r = await fetch('https://api.sirgiving.org/v1/partner/donations/create-link', {
method: 'POST',
headers: {
'X-Partner-Key': 'pk_live_...', // publishable key, safe in browser
'Content-Type': 'application/json',
},
body: JSON.stringify({
nonprofitSlug: 'american-red-cross',
amount: 2500, // $25.00 in cents
currency: 'USD',
frequency: 'once',
provider: 'stripe',
donorEmail: 'jane@example.com',
firstName: 'Jane',
lastName: 'Doe',
}),
});
const { donationLink, partnerDonationId } = await r.json();
window.location.href = donationLink;
What to do next
partnerDonationIdaction.completedaction.failedScenario 3 —3: Reverse a reward whenafter ana orderrefund
If the real-world action is refundedreversed,
Goal: A customer returns the widget you rewarded them for in Scenario 1. Pullreverse the SIR tokensreward back.too.
Flow
[Refund initiated]
│
▼
[POST /v1/partner/actions/{actionId}/reverse]
│
├─ tokens clawed back from user wallet
├─ if user already spent some, a "debt" is created
└─ webhook: action.reversed
Request
POST /v1/partner/actions/65f1.../reverse
X-Partner-Key: sk_live_.sk_test_...
#X-Timestamp: MUST<timestamp>
beX-Signature: sk;<signature>
pkContent-Type: rejected
…HMAC headers…application/json
{
"reversalPercentage": 100,
"reason": "Order refunded - return processed"refunded",
"refundIdempotencyKey": "refund_order_98765"
}
Required scope:the API key must haveactions:reversein its scope list. Otherwise →403 INSUFFICIENT_SCOPE. Ask your admin contact to attach the scope when generating the key.
Response
{
"reversalTransactionId": "...",
"tokensClawedBack": 35,
"debtsCreated": 1,
"breakdown": [
{ "stakeholderTypeCode": "CUSTOMER", "expected": 50, "clawedBack": 35, "debt": 15 }
]
}
debtsCreated > 0 meansIf the user had already redeemedspent some ofrewards, theSIR rewardedGiving tokens.may Thecreate a debt isthat tracked on their account and will offsetoffsets future earnings.
Scenario 44: —Register Runand test a 2X holiday campaignwebhook
Goal:
Step Double1: SIR rewards for all donations between Black Friday and New Year's.
Steps
GET /v1/partner/token-poolsPOST /v1/partner/campaigns
{
"tokenPoolId": "...",
"name": "Holiday 2X",
"templateId": "REWARD_MULTIPLIER",
"actionTypes": ["DONATION"],
"distribution": { "baseMultiplier": 2.0, "stakeholderSplits": [...] },
"limits": { "totalBudget": 1000000 },
"schedule": { "startDate": "2026-11-28T00:00:00Z", "endDate": "2026-12-31T23:59:59Z" }
}
PATCH /v1/partner/campaigns/:id { "status": "ACTIVE" }
campaign.activatedactionTypecampaignIdGET /v1/partner/campaigns/:id/analyticscampaign.budget_reachedcampaign.completedendDatePATCH … { status: "PAUSED" }Scenario 5 — Top up your token pool when low
Goal: Catch low balances before they cause 402 errors in production.
Steps
token_pool.low_balancePOST /v1/partner/token-pools/request
{
"amount": 500000,
"reason": "Q4 forecast: 25% over allocation, current pool 8% remaining"
}
token_pool_request.approved…rejectedavailableBalancetoken_pool.refilledYou can also enable auto-refill on the pool (admin-side) so this happens without manual intervention — autoRefill.enabled: true with a threshold, refill amount, and optional admin-approval gate.
Scenario 6 — Set up a webhook from scratch and verify it
# 1. Register
curl -X POST https://api.sirgiving.org/v1/partner/webhooks
\
-H "X-Partner-Key: $SK"sk_test_...
\
-H "X-Timestamp: $TS"<timestamp>
\
-H "X-Signature: $SIG"<signature>
\
-H "Content-Type: application/json"json
\
-d '{
"url": "https://your-app.example.com/webhooks/sir",
"eventTypes": ["action.completed", "action.failed"],
"receiveAllEvents": false
}'
#
Store the returned `secret`webhook securelysecret.
Step 2.2: Send a test pingevent
POST https://api.sirgiving.org/v1/partner/webhooks/<id>/:id/test
\
-H "X-Partner-Key: $SK"sk_test_...
-H "X-Timestamp: $TS"<timestamp>
-H "X-Signature: $SIG"<signature>
#
Step 3: Inspect deliveriesdelivery curlhistory
GET //api.sirgiving.org/v1/partner/webhooks/<id>/deliveries?limit=20":id/deliveries
\
-H "X-Partner-Key: $SK"sk_test_...
-H "X-Timestamp: $TS"<timestamp>
-H "X-Signature: $SIG"<signature>
If
Scenario 5: Preflight before a deliverybatch shows status: FAILED, hit:
curl -X POST https://api.sirgiving.org/v1/partner/webhooks/<id>/deliveries/<deliveryId>/retry …
Scenario 7 — Health check before your daily batchrun
A 5-call preflight beforeBefore submitting a 10k-actionlarge bulk run:batch:
GET /v1/partner/dashboard/integration-health— pipelines green?GET /v1/partner/token-pools/<id>/:id/balance— enough headroom?GET /v1/partner/campaigns/active— right campaign live?GET /v1/partner/dashboard/webhooks/health— webhook success rate >99%?GET /v1/partner/dashboard/api-usage?days=1— anywhere near rate limit?
Then submit inup batches ofto 100 viaactions at a time:
POST /v1/partner/actions/bulk.
Common mistakesTroubleshooting
INVALID_SIGNATURE
Signed path/body does not match request
Sign exact path and exact body bytes
NO_SANDBOX_POOL
Sandbox key is valid but no sandbox pool exists
Ask SIR Giving to provision a sandbox pool
No active token pool found
Production pool missing or inactive
Confirm pool allocation before launch
403 on write endpoint
You used pk_... or missing scope
Use sk_... and request needed scope
Duplicate reward concern
Retried request after timeout
Reuse the same idempotencyKey
order_98765donation_2024INSUFFICIENT_SCOPEsk_actions:reversesk_409X-RateLimit-Remaining