Skip to main content

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

    A publishable key, such as 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

      A secret key, such as [Customersk_test_.... checksThe out]matching ───HMAC yoursecret. storeAn ───┐ │ [Your backend: order.completed] │ │ [POST /v1/partner/actions/submit] ───── SIR API ─────┘ │ ├─active token pool debitedin ├─the same environment. A stable user accountidentifier credited └─ webhook: action.completed →from your endpointsystem.

      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

        Show the user a "+50 SIR" toast (or whatever your campaign multiplier yields). Persist

        Store actionId againstwith your orderorder. soYou need it for refunds or support.

        What you canbuilt

        later

        You reversenow it onhave a refund.

        backend Listenreward forflow. action.completedYour ifsystem yousends wantone server-sidesigned confirmationrequest, ratherSIR thanGiving trusting the response.

        Failure modes

          402 INSUFFICIENT_POOL —debits your token poolpool, is empty. Resolve by POST /v1/partner/token-pools/request for more allocation. 409 IDEMPOTENT — same idempotencyKey was already processed; you getcredits the originalpartner responseuser, back.and Safereturns toa retry.completed 429 — rate-limited. Honor Retry-After.
          result.

          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

            Save partnerDonationId against the user session to show a "Thanks for donating!" page on return. Subscribe to action.completed and action.failed server-side to definitively know when tokens land.

            Scenario 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 have actions:reverse in 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

              Pick a token pool to fundRegister the campaign: GET /v1/partner/token-pools → identify the pool with enough headroom. Create the campaign in DRAFT:webhook
              POST /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" }
              }
              
              Activate when ready:
              PATCH /v1/partner/campaigns/:id   { "status": "ACTIVE" }
              
              Webhook: campaign.activated. Submit actions normally. The active campaign is matched automatically by actionType. (Or pass campaignId explicitly.) Monitor:
                GET /v1/partner/campaigns/:id/analytics → stakeholder + tier breakdown, daily trends, budget %. Subscribe to campaign.budget_reached and campaign.completed. Wind down: the campaign auto-completes at endDate, or you can PATCH … { status: "PAUSED" } early.

                Scenario 5 — Top up your token pool when low

                Goal: Catch low balances before they cause 402 errors in production.

                Steps

                  Subscribe to token_pool.low_balance on your webhook. Threshold is configurable on the pool. On webhook arrival, request more allocation:
                  POST /v1/partner/token-pools/request
                  {
                    "amount": 500000,
                    "reason": "Q4 forecast: 25% over allocation, current pool 8% remaining"
                  }
                  
                  Wait for admin review. Webhook fires token_pool_request.approved (or …rejected). On approval, the pool's availableBalance increases. Subscribe to token_pool.refilled to confirm.

                  You 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
                  }'
                  #
                  → save

                  Store the returned `secret`webhook securelysecret.

                  #

                  Step 2.2: Send a test pingevent

                  curl -X
                  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>
                  #
                  3.

                  Step 3: Inspect deliveriesdelivery curlhistory

                  "https:
                  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:

                  1. GET /v1/partner/dashboard/integration-health — pipelines green?
                  2. GET /v1/partner/token-pools/<id>/:id/balance — enough headroom?
                  3. GET /v1/partner/campaigns/active — right campaign live?
                  4. GET /v1/partner/dashboard/webhooks/health — webhook success rate >99%?
                  5. 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

                    Same Problem Likely cause Fix 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 across two differentWebhook actions.repeats TheYour secondendpoint calldid willnot return the2xx firstfast action'senough response.Queue Make keys unique per logical operation, e.g. order_98765 (not donation_2024). Reusing publishable keys for write endpoints. They reject with 403work and you'll see INSUFFICIENT_SCOPE-style errors. Use sk_ for writes. Forgetting actions:reverse scope. Reversal endpoint will 403 even with a valid sk_ key if the scope isn't on the key. Get the key re-issued with the scope. Not handling 409 on retries. Idempotent POSTs can return yourquickly original result on retry — that's a feature, not a failure. Ignoring X-RateLimit-Remaining. If you batch in tight loops, slow down preemptively rather than hitting 429s. Webhook handler does work synchronously. Return 200 fast and offload to a queue. Slow handlers cause retries and can get your webhook auto-disabled.