Creating an Endpoint
Register a URL and specify which events you want to receive:secret starting with whsec_. Store it immediately. It cannot be retrieved later. You’ll use it to verify signatures.
"enabled_events": ["*"] to subscribe to all event types.
Event Types
Redemption lifecycle events
Authorization emitsredemption.pending. Capture emits
redemption.completed; failure and timeout emit redemption.failed; and
cancellation emits redemption.cancelled.
Pending-redemption resolution payloads include status. Failure and
cancellation include failure_reason when supplied, while an automatic timeout
uses failure_reason: "expired". Instant completion keeps the create-time
payload shape.
Reward status events
reward.status_changed fires when a reward’s status changes between DRAFT,
ACTIVE, OUT_OF_STOCK, and ARCHIVED.
PATCH /v1/programs/{programId}/rewards/{rewardId} emits it when status
changes. Same-status writes and non-status field updates do not. Creating a
reward does not emit it; use the create response.
For UNIT_BASED rewards with max_total, authorization and instant redemption
can move ACTIVE to OUT_OF_STOCK. Fail, cancel, timeout, and reversal can move
OUT_OF_STOCK back to ACTIVE. Under async fulfillment that cycle can repeat.
Treat OUT_OF_STOCK as live inventory state, not a terminal catalog state. See
Rewards catalog.
Payload Format
Every delivery sends a JSON envelope:
For the
data object each event type delivers, see the event payloads reference.
Signature Verification
Every delivery includes aScrip-Signature header so you can verify it came from Scrip and wasn’t tampered with.
Header Format
Verification Steps
1
Extract components
Parse the
t and v1 values from the Scrip-Signature header.2
Construct signed payload
Concatenate the timestamp, a literal dot, and the raw request body:
{t}.{raw_body}3
Compute expected signature
Calculate
HMAC-SHA256(your_endpoint_secret, signed_payload) and hex-encode the result.4
Compare signatures
Use constant-time comparison. Reject the request if they don’t match.
5
Check timestamp
Reject if
abs(now - t) exceeds your tolerance. We recommend 5 minutes.Example (Python)
Example (Node.js)
Retry Policy
If your endpoint has a retryable failure such as a 5xx response, network error, or timeout, Scrip retries with exponential backoff:
After 8 attempts (~10.5 hours), the delivery is marked
FAILED. You can manually resend any terminal delivery to send the event again.
Response Handling
Return a 2xx quickly (within 30 seconds). Process the payload asynchronously if your handler needs more time. The worker enforces a 30-second timeout per delivery attempt.
Delivery Error Codes
Failed attempts include a stableerror_code alongside the human-readable last_error. Use error_code for alerts and dashboards.
Managing Endpoints
Disable and Re-enable
Temporarily stop deliveries without deleting the endpoint:error_code: "endpoint_disabled". Set status back to ACTIVE to receive future matching events. Events that occurred while the endpoint was disabled are not retroactively delivered.
If you disabled an endpoint temporarily and still want to deliver specific failed events, re-enable the endpoint and resend the terminal delivery records.
Rotate Secret
If a secret is compromised, rotate it immediately:Delete
Deleting an endpoint archives it. It stops receiving deliveries and is removed from list results:Endpoint Health
Scrip monitors delivery health per endpoint. A bad destination can be paused temporarily, then auto-disabled if it keeps failing or stops draining its backlog.
Use delivery stats to see whether an endpoint is healthy, paused, disabled, or building a backlog:
success_24h, fail_24h, success_rate, degraded) plus current backlog and block fields: pending_count, oldest_pending_at, circuit_broken_until, rate_limited_until, blocked_reason, and blocked_until.
To re-enable an endpoint after resolving the underlying issue:
429 responses do not count toward the failure-rate breaker. However, if rate limiting creates a sustained, non-draining backlog, Scrip can auto-disable the endpoint with error_code: "endpoint_backlog_exceeded". Re-enable the endpoint and resend any failed deliveries you still need.
Debugging Deliveries
List Deliveries for an Endpoint
Inspect a Delivery
The detail endpoint includeslast_response_status, last_response_body (truncated to 4 KB), last_error, error_code, and resend_seq:
Resend a Delivery
Resend issues a new delivery of the same event to the same endpoint: a fresh delivery with its ownid and an incremented resend_seq. The source delivery is left untouched, so you keep a complete history of every attempt.
PENDING), which you can poll like any other. Use resend to replay a webhook after fixing a bug in your handler, or to confirm your endpoint deduplicates correctly.
Only terminal deliveries can be resent, and the endpoint must be active:
Best Practices
Delivery Guarantees
Webhook events are created atomically with their domain operations. If the underlying transaction rolls back, no webhook is emitted. Retried domain operations (like event reprocessing) do not produce duplicate webhooks. Delivery is at-least-once: a single event may be delivered more than once if your endpoint returns a 2xx but the acknowledgment is lost in transit. Design your handler to be idempotent using the envelope’sid field to detect duplicates.