Add SendPromptly to One Stripe Flow

Instrument one Stripe payment path with SendPromptly in an afternoon.

Start with the Stripe payment path that already causes support pain. SendPromptly adds two HTTP calls to your existing webhook handler. You do not need to change how Stripe delivers events or how your endpoint works.

Prerequisites

  • Active SendPromptly account — start a 14-day trial
  • An existing Stripe webhook handler in your app
  • A dedicated repair endpoint you control (used only for reprocess callbacks)

Step 1 — Create a project

In app.sendpromptly.com, create a project. Copy the API key and store it server-side only — never expose it in client code.

Configure your repair callback URL. This is the endpoint SendPromptly will POST to when you trigger a reprocess from the incident console.

Step 2 — Choose one event to instrument first

Start with one Stripe event type:

  • checkout.session.completed — for access unlock failures
  • invoice.paid — for credit grant or subscription-state failures

Do not try to instrument every Stripe event at once. One flow first, then expand.

Step 3 — Send receipt

After your webhook handler verifies the Stripe signature and durably accepts the work (for example, enqueues a job), send a receipt to SendPromptly:

1
2
3
4
POST https://api.sendpromptly.app/v1/receipt
Authorization: Bearer <project_api_key>
Idempotency-Key: <deterministic_unique_key>
Content-Type: application/json
1
2
3
4
5
6
7
8
{
  "provider": "stripe",
  "stripe_event_id": "evt_123",
  "event_type": "checkout.session.completed",
  "effect_type": "access_unlock",
  "internal_reference": "user_456",
  "occurred_at": "2026-05-05T15:04:05Z"
}

Send the receipt before your job queue runs the business effect — not after.

Supported effect_type values: access_unlock, credit_grant, seat_update, subscription_state_apply, access_revocation, credit_deduction.

Step 4 — Send result

After your app attempts the business effect, send the result. For success:

1
2
3
4
{
  "stripe_event_id": "evt_123",
  "status": "success"
}

For failure:

1
2
3
4
5
6
{
  "stripe_event_id": "evt_123",
  "status": "failed",
  "error_code": "access_update_failed",
  "error_message": "User row lock timeout"
}

If no result arrives within the configured timeout (default 30 minutes), SendPromptly opens an incident automatically. You can override the timeout per event using "timeout_minutes" in the receipt payload.

Step 5 — Implement your repair endpoint

SendPromptly POSTs a signed callback to your repair endpoint when you trigger a reprocess from the incident console. Verify the signature before processing.

Signature headers:

1
2
3
X-SendPromptly-Timestamp: <unix_timestamp>
X-SendPromptly-Nonce: <nonce>
X-SendPromptly-Signature: <hmac_sha256>

The signature is HMAC-SHA256 over timestamp + "." + nonce + "." + raw_body using your project’s signing secret.

Reject callbacks with a timestamp older than 5 minutes. Your repair endpoint must be idempotent — it will receive the same replay_id on retries.

Callback payload:

1
2
3
4
5
6
7
8
{
  "replay_id": "rp_123",
  "stripe_event_id": "evt_123",
  "event_type": "checkout.session.completed",
  "effect_type": "access_unlock",
  "internal_reference": "user_456",
  "requested_at": "2026-05-05T15:40:00Z"
}

After running your repair logic, send a result with the replay_id included:

1
2
3
4
5
{
  "stripe_event_id": "evt_123",
  "status": "success",
  "replay_id": "rp_123"
}

Step 6 — Test the integration

  1. Send a test checkout.session.completed event via Stripe’s test dashboard
  2. Confirm your app sends a receipt — check the SendPromptly console
  3. Force a failure result to trigger an incident
  4. Confirm the email alert arrives
  5. Click Reprocess from the incident console
  6. Confirm your repair endpoint receives the signed callback
  7. Send a success result
  8. Confirm the incident resolves

Language examples

Node.js:

1
2
3
4
5
6
7
8
9
await fetch("https://api.sendpromptly.app/v1/receipt", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.SENDPROMPTLY_KEY}`,
    "Idempotency-Key": crypto.randomUUID(),
    "Content-Type": "application/json"
  },
  body: JSON.stringify(payload)
})

PHP (Laravel):

1
2
3
Http::withToken(config('services.sendpromptly.key'))
    ->withHeaders(['Idempotency-Key' => (string) Str::uuid()])
    ->post('https://api.sendpromptly.app/v1/receipt', $payload);

Python:

1
2
3
4
5
6
7
8
9
requests.post(
    "https://api.sendpromptly.app/v1/receipt",
    headers={
        "Authorization": f"Bearer {os.environ['SENDPROMPTLY_KEY']}",
        "Idempotency-Key": str(uuid.uuid4())
    },
    json=payload,
    timeout=5
)

What not to instrument first

  • Every Stripe event type — instrument one, validate it, then expand
  • Full subscription lifecycle edge cases — invoice.paid and checkout.session.completed cover the highest-risk paths at launch
  • Generic webhook logging — that is not what SendPromptly does