Build on ScriptRun
Push orders into ScriptRun from your pharmacy software. Receive real-time delivery events on your server. Every endpoint is authenticated, idempotent where it matters, and documented with a working curl example.
Quick start
Push your first order in under five minutes.
- Sign in, open Settings → Integrations, and create an API key. Copy it — it's shown once.
- POST a single order to
/api/webhook/orderswith the key in theX-API-Keyheader. - Watch it appear in your Dashboard in real time.
bash curlcurl -X POST https://scriptrun.app/api/webhook/orders \ -H "X-API-Key: sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "orders": [{ "patient_name": "Maria Rodriguez", "patient_address": "123 Main St, Miami, FL 33178", "patient_phone": "+13055551234", "rx_number": "RX-8872341", "copay_amount": 12.50, "priority": "routine", "requires_signature": true }] }'
Response 200 OK:
json{ "created": 1, "package_ids": ["a3e9f1..."], "warnings": [] }
Authentication
All API requests are authenticated with an API key passed in the X-API-Keyheader. Keys are scoped to a single tenant (pharmacy or dispatcher account) and inherit that tenant's plan limits and feature flags.
- Keys are stored hashed (SHA-256); the plaintext is never retrievable.
- Revoke a key anytime from the Integrations page. Revocation is immediate.
- Prefix
sk_live_denotes a production key. Prefixsk_test_denotes a test/sandbox key.
Push orders
POST /api/webhook/orders accepts a batch of up to 100 orders in a single request. Each order is validated individually — partial success is possible and is reported in the response.
Request schema
Multiple orders in one call:
jsonPOST /api/webhook/orders X-API-Key: sk_live_... { "orders": [ { "patient_name": "A. Smith", "patient_address": "1 Ocean Dr, Miami Beach FL" }, { "patient_name": "B. Jones", "patient_address": "200 Biscayne, Miami FL", "priority": "urgent" } ] }
Outbound webhooks
Subscribe to real-time events on delivery lifecycle. Configure destination URL and events from Settings → Integrations → Webhooks. Requires a Pro plan or higher.
- Each webhook endpoint gets a signing secret — returned once at creation.
- Deliveries use HTTPS POST with a JSON body.
- Non-2xx responses are retried with exponential backoff for up to 24 hours.
- Delivery attempts are logged in
webhook_logsfor audit.
Event types
Example payload: delivery.completed
jsonPOST https://your-server.example.com/webhooks/scriptrun Content-Type: application/json X-ScriptRun-Signature: t=1713547200,v1=5a3e... X-ScriptRun-Event: delivery.completed { "id": "evt_9f2c...", "event": "delivery.completed", "created_at": "2026-04-19T18:42:10Z", "data": { "package_id": "a3e9f1...", "external_patient_id": "PMS-9081", "rx_number": "RX-8872341", "driver_id": "drv_b12c...", "delivered_at": "2026-04-19T18:41:57Z", "proof": { "photo_url": "https://storage.scriptrun.app/...", "signature_url": "https://storage.scriptrun.app/...", "lat": 25.8612, "lng": -80.2983 }, "copay_collected": 12.50, "copay_method": "card" } }
Signing & verification
Every outbound webhook is signed with HMAC-SHA256 using the secret you were given at creation. Verify before trusting the payload.
typescriptimport crypto from 'node:crypto' function verify(rawBody: string, header: string, secret: string) { // header format: "t=<unix_ts>,v1=<sig>" const parts = Object.fromEntries(header.split(',').map(p => p.split('='))) const signed = `${parts.t}.${rawBody}` const expected = crypto.createHmac('sha256', secret).update(signed).digest('hex') if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))) { throw new Error('Invalid signature') } // reject payloads older than 5 minutes if (Math.abs(Date.now()/1000 - Number(parts.t)) > 300) { throw new Error('Stale webhook') } }
CSV import
Don't have engineering resources? Upload a CSV from the Packages page. The dispatcher can drag and drop a spreadsheet export from your PMS. Here's the expected format.
csvpatient_name,patient_address,patient_phone,rx_number,copay_amount,priority,delivery_date,notes Maria Rodriguez,"123 Main St, Miami, FL 33178",+13055551234,RX-8872341,12.50,routine,2026-04-20,"Leave with doorman" Alex Kim,"450 Collins Ave, Miami Beach, FL 33139",+13055559876,RX-8872589,0.00,urgent,2026-04-20,
Columns map to the same field names as the JSON API. Unknown columns are ignored. Empty cells become null. Rows with validation errors are reported individually; the good rows still import.
Shopify
Selling pharmacy goods on Shopify? Install our app from Settings → Integrations → Shopify. We listen for orders/create and orders/paid webhooks and create packages automatically. Fulfillment and refund events flow back to Shopify on delivery.completed / delivery.failed.
Rate limits
Limits are per API key. If you need higher throughput, email us.
- Orders ingest: 600 requests/minute · 100 orders/batch · 10,000 orders/hour
- Read endpoints: 60 requests/minute
- OCR / label scan: 30 requests/minute
Hitting a limit returns 429 Too Many Requests with a Retry-After header. Back off with jitter.
Errors
Responses follow standard HTTP status codes.
200 OK— success400 Bad Request— validation failure. Response body haserroranddetailsarray.401 Unauthorized— missing or invalidX-API-Key.402 Payment Required— plan limit exceeded and no overage billing enabled.403 Forbidden— feature not available on current plan.429 Too Many Requests— rate limited.5xx— our fault. Retry with backoff.
Support
Engineering questions: developers@scriptrun.app. Typical response within one business day.
Reporting a security issue: security@scriptrun.app. See /security.