REST API Documentation
Integrate CostDock into your applications. Create shipping labels, validate addresses, track packages, and manage batches programmatically.
Authentication
All API requests require a valid API key passed as a Bearer token in the Authorization header. Generate a key from your account settings.
curl -H "Authorization: Bearer sk_live_your_api_key_here" \
https://app.costdock.com/api/v1/creditsSecurity: Keep your API key private. Never expose it in client-side code or commit it to version control.
Response Format
All responses follow a consistent JSON structure:
Success
{
"success": true,
"data": { ... }
}Error
{
"success": false,
"error": {
"code": "INSUFFICIENT_CREDITS",
"message": "Not enough credits"
}
}Rate Limiting
API requests are limited to 100 requests per minute per API key. Rate limit info is included in response headers:
| Header | Description |
|---|---|
| X-RateLimit-Limit | Requests allowed per window |
| X-RateLimit-Remaining | Requests remaining |
| X-RateLimit-Reset | Unix timestamp when window resets |
| Retry-After | Seconds to wait (only on 429 responses) |
Idempotency
All POST requests (rates, shipments, batches) require an Idempotency-Key header. If you retry a request with the same key within 24 hours, you'll get the original response back without creating a duplicate.
curl -X POST \
-H "Authorization: Bearer sk_live_..." \
-H "Idempotency-Key: unique-request-id-123" \
-H "Content-Type: application/json" \
-d '{"from": {...}, "to": {...}, "parcel": {...}}' \
https://app.costdock.com/api/v1/shipmentsEndpoints
/api/v1/creditsGet your current credit balance.
Request
curl -H "Authorization: Bearer sk_live_..." \
https://app.costdock.com/api/v1/creditsResponse
{
"success": true,
"data": {
"balance_cents": 5000,
"formatted_balance": "$50.00"
}
}/api/v1/ratesGet shipping rates for a package. Returns available carriers, services, and prices.
Request
curl -X POST \
-H "Authorization: Bearer sk_live_..." \
-H "Idempotency-Key: rates-unique-id-123" \
-H "Content-Type: application/json" \
-d '{
"from": {
"name": "John Doe",
"street1": "123 Main St",
"city": "San Francisco",
"state": "CA",
"zip": "94105",
"country": "US"
},
"to": {
"name": "Jane Smith",
"street1": "456 Oak Ave",
"city": "Los Angeles",
"state": "CA",
"zip": "90001",
"country": "US"
},
"parcel": {
"packageType": "package",
"lengthIn": 10,
"widthIn": 8,
"heightIn": 4,
"weightLbs": 1,
"deliveryOption": "none"
}
}' \
https://app.costdock.com/api/v1/ratesResponse
{
"success": true,
"data": {
"rates": [
{
"carrier": "USPS",
"service": "Priority",
"rate_cents": 825,
"delivery_days": 3
}
]
}
}Dimensions required for packages: lengthIn, widthIn, and heightIn are required when packageType is "package". UPS and FedEx cannot return rates without dimensions. Omitting them will limit results to USPS only. Weight can be specified as weightLbs (recommended) or weightOz (if both provided, weightOz takes precedence). Optional fields: deliveryOption ("none" | "signature" | "adult_signature"), isReturn (boolean), skip_address_verification (boolean, default false — skip address verification for addresses that fail validation but are known to be correct).
/api/v1/addresses/validateValidate and normalize an address. Returns corrected address with residential/commercial indicator.
Request
curl -X POST \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"address": {
"name": "John Doe",
"street1": "123 Main St",
"city": "San Francisco",
"state": "CA",
"zip": "94105",
"country": "US"
}
}' \
https://app.costdock.com/api/v1/addresses/validateResponse
{
"success": true,
"data": {
"valid": true,
"address": {
"street1": "123 MAIN ST",
"street2": null,
"city": "SAN FRANCISCO",
"state": "CA",
"zip": "94105",
"country": "US",
"name": "JOHN DOE",
"company": null,
"phone": null,
"residential": false
}
}
}/api/v1/shipments?page=1&per_page=20List your shipments with pagination.
Request
curl -H "Authorization: Bearer sk_live_..." \
"https://app.costdock.com/api/v1/shipments?page=1&per_page=20"Response
{
"success": true,
"data": {
"shipments": [...],
"total_count": 42,
"page": 1,
"per_page": 20
}
}/api/v1/shipmentsCreate and purchase a shipping label. Returns 201 Created. Optionally specify carrier and service from a rates response — otherwise the cheapest rate is selected automatically. Deducts credits.
Request
curl -X POST \
-H "Authorization: Bearer sk_live_..." \
-H "Idempotency-Key: ship-456" \
-H "Content-Type: application/json" \
-d '{
"from": {
"name": "John Doe",
"company": "Acme Corp",
"street1": "123 Main St",
"street2": "Suite 400",
"city": "San Francisco",
"state": "CA",
"zip": "94105",
"country": "US",
"phone": "415-555-0100"
},
"to": {
"name": "Jane Smith",
"street1": "456 Oak Ave",
"city": "Los Angeles",
"state": "CA",
"zip": "90001",
"country": "US"
},
"parcel": {
"packageType": "package",
"lengthIn": 10,
"widthIn": 8,
"heightIn": 4,
"weightLbs": 1,
"deliveryOption": "signature",
"labelFormat": "PDF"
},
"carrier": "USPS",
"service": "Priority",
"insurance_value": 150.00
}' \
https://app.costdock.com/api/v1/shipmentsResponse
{
"success": true,
"data": {
"id": "uuid",
"tracking_code": "1Z999AA10123456784",
"label_url": "https://...",
"label_pdf_url": "https://...",
"label_zpl_url": "https://...",
"carrier": "USPS",
"service": "Priority",
"rate_cents": 877,
"status": "purchased",
"public_tracking_url": "https://...",
"created_at": "2026-02-20T00:00:00Z",
"insurance": {
"declared_value_cents": 15000,
"cost_cents": 150
}
}
}Optional fields: Address objects also accept company, street2, and phone. Set packageType to envelope for letters and flat mailers (no dimensions needed, cheaper rates). Parcel also accepts deliveryOption (signature, adult_signature) and labelFormat (PNG, PDF, ZPL). Set skip_address_verification to true to bypass address verification for addresses that fail validation but are known to be correct. See Address Fields and Parcel Fields for the full schema.
Optional insurance: Include insurance_value (USD, $1.00–$2000.00) to insure the shipment against loss or damage. Cost is pass-through at 1% of declared value with a $0.50 minimum (e.g. $150 coverage costs $1.50). Deducted from credits in the same charge as the label — a single atomic hold. The insurance object on the response returns declared_value_cents and cost_cents, or null when no insurance was purchased.
Two-step flow: Call POST /api/v1/rates first to show available carriers and prices, then pass the customer's chosen carrier and service to POST /api/v1/shipments. If omitted, the cheapest available rate is selected automatically.
/api/v1/shipments/:idGet details for a specific shipment.
Request
curl -H "Authorization: Bearer sk_live_..." \
https://app.costdock.com/api/v1/shipments/uuid-hereResponse
{
"success": true,
"data": {
"id": "uuid",
"tracking_code": "1Z999AA10123456784",
"label_url": "https://...",
"label_pdf_url": "https://...",
"label_zpl_url": "https://...",
"carrier": "USPS",
"service": "Priority",
"rate_cents": 877,
"status": "purchased",
"public_tracking_url": "https://...",
"created_at": "2026-02-20T00:00:00Z",
"insurance": null
}
}/api/v1/shipments/:id/voidVoid a purchased label and refund credits. Subject to carrier-specific void windows (USPS: 30 days, UPS/FedEx: 90 days).
Request
curl -X POST \
-H "Authorization: Bearer sk_live_..." \
https://app.costdock.com/api/v1/shipments/uuid-here/voidResponse
{
"success": true,
"data": {
"id": "uuid",
"status": "voided",
"refunded_amount_cents": 825
}
}Orders (Multi-Package Shipments)
Ship multiple packages to the same destination in a single order. UPS and FedEx offer MPS (multi-piece shipment) discounts when packages are grouped together. Minimum 2 parcels, maximum 100.
/api/v1/orders/ratesGet rates for a multi-package order. Returns rates with MPS discounts applied when available.
Request
curl -X POST \
-H "Authorization: Bearer sk_live_..." \
-H "Idempotency-Key: rates-unique-id" \
-H "Content-Type: application/json" \
-d '{
"from": {
"name": "John Doe",
"company": "Acme Corp",
"street1": "123 Main St",
"city": "San Francisco",
"state": "CA",
"zip": "94105",
"country": "US",
"phone": "415-555-0100"
},
"to": {
"name": "Jane Smith",
"street1": "456 Oak Ave",
"city": "Los Angeles",
"state": "CA",
"zip": "90001",
"country": "US"
},
"parcels": [
{
"packageType": "package",
"lengthIn": 10,
"widthIn": 8,
"heightIn": 4,
"weightLbs": 2
},
{
"packageType": "package",
"lengthIn": 12,
"widthIn": 10,
"heightIn": 6,
"weightLbs": 5
}
]
}' \
https://app.costdock.com/api/v1/orders/ratesResponse
{
"success": true,
"data": {
"rates": [
{
"carrier": "UPS",
"service": "Ground",
"rate_cents": 1450,
"delivery_days": 5
},
{
"carrier": "FedEx",
"service": "Ground",
"rate_cents": 1520,
"delivery_days": 5
}
],
"parcel_count": 2
}
}/api/v1/ordersCreate and purchase a multi-package order. Returns 201 Created. Each parcel gets its own tracking number and label. Credits are deducted for the total cost in a single atomic charge. Requires an Idempotency-Key header.
Request
curl -X POST \
-H "Authorization: Bearer sk_live_..." \
-H "Idempotency-Key: order-789" \
-H "Content-Type: application/json" \
-d '{
"from": {
"name": "John Doe",
"company": "Acme Corp",
"street1": "123 Main St",
"city": "San Francisco",
"state": "CA",
"zip": "94105",
"country": "US",
"phone": "415-555-0100"
},
"to": {
"name": "Jane Smith",
"street1": "456 Oak Ave",
"city": "Los Angeles",
"state": "CA",
"zip": "90001",
"country": "US"
},
"parcels": [
{
"packageType": "package",
"lengthIn": 10,
"widthIn": 8,
"heightIn": 4,
"weightLbs": 2
},
{
"packageType": "package",
"lengthIn": 12,
"widthIn": 10,
"heightIn": 6,
"weightLbs": 5
}
],
"carrier": "UPS",
"service": "Ground"
}' \
https://app.costdock.com/api/v1/ordersResponse
{
"success": true,
"data": {
"order_id": "uuid",
"shipments": [
{
"id": "uuid-1",
"tracking_code": "1Z999AA10123456784",
"label_url": "https://...",
"label_pdf_url": "https://...",
"carrier": "UPS",
"service": "Ground",
"rate_cents": 725
},
{
"id": "uuid-2",
"tracking_code": "1Z999AA10123456785",
"label_url": "https://...",
"label_pdf_url": "https://...",
"carrier": "UPS",
"service": "Ground",
"rate_cents": 725
}
],
"total_cost_cents": 1450
}
}Idempotency-Key required: The POST /api/v1/orders endpoint requires an Idempotency-Key header to prevent duplicate purchases. Use a unique value per order (e.g. your internal order ID).
/api/v1/tracking/:codeGet tracking status and events for a shipment.
Request
curl -H "Authorization: Bearer sk_live_..." \
https://app.costdock.com/api/v1/tracking/1Z999AA10123456784Response
{
"success": true,
"data": {
"tracking_code": "1Z999AA10123456784",
"status": "in_transit",
"events": [
{
"status": "in_transit",
"message": "Package in transit",
"datetime": "2026-02-12T10:00:00Z",
"location": "San Francisco, CA"
}
]
}
}/api/v1/batches?page=1&per_page=20List your batches with pagination.
Request
curl -H "Authorization: Bearer sk_live_..." \
"https://app.costdock.com/api/v1/batches?page=1&per_page=20"Response
{
"success": true,
"data": {
"batches": [...],
"total_count": 5,
"page": 1,
"per_page": 20
}
}/api/v1/batchesCreate a batch of shipments for bulk label purchase. Returns 201 Created. Accepts 1-1000 shipments.
Request
curl -X POST \
-H "Authorization: Bearer sk_live_..." \
-H "Idempotency-Key: batch-789" \
-H "Content-Type: application/json" \
-d '{
"shipments": [
{
"from": {
"name": "John Doe",
"company": "Acme Corp",
"street1": "123 Main St",
"street2": "Suite 400",
"city": "San Francisco",
"state": "CA",
"zip": "94105",
"country": "US",
"phone": "415-555-0100"
},
"to": {
"name": "Jane Smith",
"street1": "456 Oak Ave",
"city": "Los Angeles",
"state": "CA",
"zip": "90001",
"country": "US"
},
"parcel": {
"packageType": "package",
"lengthIn": 10,
"widthIn": 8,
"heightIn": 4,
"weightLbs": 1,
"deliveryOption": "signature",
"labelFormat": "PDF"
}
},
{
"from": {...},
"to": {...},
"parcel": {...}
}
]
}' \
https://app.costdock.com/api/v1/batchesResponse
{
"success": true,
"data": {
"id": "uuid",
"status": "draft",
"total_shipments": 2,
"created_at": "2026-02-20T00:00:00Z"
}
}Optional fields: Each shipment's address accepts company, street2, and phone. Set packageType to envelope for letters and flat mailers (no dimensions needed, cheaper rates). Parcel also accepts deliveryOption (signature, adult_signature) and labelFormat (PNG, PDF, ZPL). See Address Fields and Parcel Fields for the full schema.
/api/v1/batches/:idGet details for a specific batch including its shipments.
Request
curl -H "Authorization: Bearer sk_live_..." \
https://app.costdock.com/api/v1/batches/uuid-hereResponse
{
"success": true,
"data": {
"id": "uuid",
"status": "completed",
"total_shipments": 10,
"purchased_count": 9,
"failed_count": 1,
"total_cost_cents": 11250,
"created_at": "2026-02-20T00:00:00Z",
"updated_at": "2026-02-20T00:15:00Z",
"shipments": [
{
"id": "uuid",
"tracking_code": "1Z999AA10123456784",
"carrier": "UPS",
"service": "Ground",
"customer_rate_cents": 1250,
"status": "purchased",
"created_at": "2026-02-20T00:15:00Z"
}
]
}
}/api/v1/batches/:id/purchasePurchase all labels in a draft batch. Verifies addresses, fetches rates, holds credits, and creates labels. Batch must be in 'draft' status.
Request
curl -X POST \
-H "Authorization: Bearer sk_live_..." \
-H "Idempotency-Key: purchase-batch-789" \
https://app.costdock.com/api/v1/batches/uuid-here/purchaseResponse
{
"success": true,
"data": {
"id": "uuid",
"status": "purchasing",
"total_shipments": 10,
"purchased_count": 9,
"failed_count": 1,
"total_cost_cents": 11250,
"errors": [
{ "index": 3, "to_name": "Jane Smith", "to_city": "Los Angeles", "error": "From address: Invalid ZIP code" }
]
}
}Claims
File loss, theft, and damage claims against insured shipments. The Claims endpoints mirror EasyPost's Claims API 1:1 — CostDock is the shipper of record on every claim and relays EasyPost's lifecycle to you via webhooks. On approved or approved_partial, the approved amount is automatically added to the owning account's credit balance (offset against future label purchases).
New integrators should consume the claim.status_changed webhook rather than polling the GET endpoints.
/api/v1/claimsFile a new claim on an insured shipment. The shipment must have insurance_value_cents > 0 and the amount cannot exceed that cap. Supports Idempotency-Key.
Request
curl -X POST https://app.costdock.com/api/v1/claims \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"shipment_id": "uuid-here",
"type": "damage",
"amount": 120.00,
"description": "Box arrived crushed, contents damaged",
"contact_email": "customer@example.com",
"attachments": {
"supporting_documentation": ["https://cdn.example.com/photo.jpg"],
"invoice": [],
"email_evidence": []
},
"metadata": { "shippy_claim_id": "shp_123" }
}'Response
{
"success": true,
"data": {
"id": "uuid",
"user_id": "uuid",
"shipment_id": "uuid",
"easypost_claim_id": "clm_abc123",
"tracking_code": "1Z999AA10123456784",
"type": "damage",
"amount_cents": 12000,
"approved_amount_cents": null,
"status": "submitted",
"description": "Box arrived crushed, contents damaged",
"contact_email": "customer@example.com",
"attachment_paths": {
"supporting_documentation": ["uid/claim_id/supporting_documentation/0.jpg"],
"invoice": [],
"email_evidence": []
},
"metadata": { "shippy_claim_id": "shp_123" },
"history": [
{ "status": "submitted", "description": "Filed", "status_timestamp": "2026-04-12T00:00:00Z" }
],
"created_at": "2026-04-12T00:00:00Z",
"updated_at": "2026-04-12T00:00:00Z"
}
}
// Note: attachment_paths values are opaque storage keys, not URLs.
// CostDock generates short-lived signed URLs on-demand for its own UI;
// integrators who need to re-display an attachment should store their own copy./api/v1/claimsList your claims in reverse-chronological order. Filter by status and paginate via a cursor on created_at.
Request
curl -H "Authorization: Bearer sk_live_..." \
"https://app.costdock.com/api/v1/claims?status=in_review&limit=50"Response
{
"success": true,
"data": {
"claims": [
{
"id": "uuid",
"shipment_id": "uuid",
"easypost_claim_id": "clm_abc123",
"tracking_code": "1Z999AA10123456784",
"type": "damage",
"status": "in_review",
"amount_cents": 12000,
"approved_amount_cents": null,
"contact_email": "customer@example.com",
"created_at": "2026-04-11T00:00:00Z",
"updated_at": "2026-04-11T00:00:00Z"
}
]
}
}
// List responses omit description, history, metadata, and attachment_paths
// to keep payloads small. Call GET /api/v1/claims/{id} for the full shape./api/v1/claims/:idRetrieve a single claim by its CostDock UUID.
Request
curl -H "Authorization: Bearer sk_live_..." \
https://app.costdock.com/api/v1/claims/uuid-hereResponse
{
"success": true,
"data": { /* full claim object — same shape as the create response */ }
}/api/v1/claims/:id/cancelCancel a claim in submitted, in_review, or needs_action status. Already-resolved claims cannot be cancelled.
Request
curl -X POST \
-H "Authorization: Bearer sk_live_..." \
https://app.costdock.com/api/v1/claims/uuid-here/cancelResponse
{
"success": true,
"data": { "id": "uuid", "status": "cancelled" }
}/api/v1/claims/:id/attachmentsAttach additional evidence to an existing claim — for example, new documents during needs_action. Files are stored in Supabase Storage under the claim; EasyPost must be re-notified manually via support (see needs_action note below).
Request
curl -X POST \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"attachments": {
"supporting_documentation": ["https://cdn.example.com/replacement.jpg"]
}
}' \
https://app.costdock.com/api/v1/claims/uuid-here/attachmentsResponse
{
"success": true,
"data": {
"attachment_paths": {
"supporting_documentation": ["uuid/claim_id/supporting_documentation/1.jpg"],
"invoice": [],
"email_evidence": []
}
}
}Status Lifecycle
submitted
└── in_review
├── approved
├── approved_partial
├── rejected
└── needs_action ──┐
(admin forwards evidence to EasyPost support manually)
└── (any terminal state, driven by EasyPost webhook)
cancelled ← may be set from submitted / in_review / needs_actionWebhook — claim.status_changed
Fired on every status transition. Deliver via your configured webhook endpoint. Payload echoes your original metadata so you can correlate back to your internal records.
{
"event": "claim.status_changed",
"data": {
"id": "uuid",
"easypost_claim_id": "clm_abc123",
"status": "approved",
"amount_cents": 12000,
"approved_amount_cents": 12000,
"metadata": { "shippy_claim_id": "shp_123" },
"updated_at": "2026-04-13T00:00:00Z"
},
"timestamp": "2026-04-13T00:00:00Z"
}Rate Limits & Requirements
POST /claims: 10 per hour per API keyPOST /claims/:id/attachments: 20 per hour per API key- GET endpoints: standard API rate limit (100/minute)
- Required fields for
theftanddamage: at least onesupporting_documentationattachment - Attachment constraints: up to 10 files per category, 10 MB each, MIME types
image/png,image/jpeg,image/webp,application/pdf
About needs_action: EasyPost uses this status when they need additional evidence from the shipper of record. CostDock stores any new attachments you upload to POST /claims/:id/attachments, but EasyPost's SDK has no API path to forward them — a CostDock admin manually relays the evidence to EasyPost support via email. The claim remains in needs_action until EasyPost replies and updates the status via webhook. You do not need to poll — wait for the claim.status_changed webhook.
Webhooks
Configure webhook endpoints to receive real-time event notifications. Manage endpoints from your account settings or via the API below.
Available Events
Webhook Payloads
Payloads vary by event type. All include type and data fields.
shipment.created / shipment.purchased
{
"type": "shipment.created",
"data": {
"id": "uuid",
"tracking_code": "1Z999AA10123456784",
"label_url": "https://...",
"carrier": "UPS",
"service": "Ground",
"rate_cents": 877,
"status": "purchased",
"public_tracking_url": "https://...",
"created_at": "2026-02-20T00:00:00Z"
}
}shipment.in_transit / shipment.delivered / shipment.returned / shipment.failed
{
"type": "shipment.in_transit",
"data": {
"id": "uuid",
"tracking_code": "1Z999AA10123456784",
"status": "in_transit",
"carrier": "UPS",
"service": "Ground",
"public_tracking_url": "https://...",
"tracking_events": [
{
"datetime": "2026-02-21T14:30:00Z",
"message": "Package in transit to destination",
"status": "in_transit",
"tracking_location": {
"city": "Memphis",
"state": "TN",
"country": "US"
}
}
]
}
}shipment.voided
{
"type": "shipment.voided",
"data": {
"id": "uuid",
"status": "voided",
"refunded_amount_cents": 877
}
}Webhook API Endpoints
/api/v1/webhooks?page=1&per_page=20List your webhook endpoints with pagination.
Request
curl -H "Authorization: Bearer sk_live_..." \
"https://app.costdock.com/api/v1/webhooks?page=1&per_page=20"Response
{
"success": true,
"data": {
"endpoints": [
{
"id": "uuid",
"url": "https://example.com/webhooks",
"enabled": true,
"events": ["shipment.created", "shipment.delivered"],
"created_at": "2026-02-20T00:00:00Z"
}
],
"total_count": 1,
"page": 1,
"per_page": 20
}
}/api/v1/webhooksCreate a new webhook endpoint. URL must use HTTPS and cannot point to private/internal addresses. Returns the signing key in the response — save it for signature verification.
Request
curl -X POST \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhooks/costdock",
"events": ["shipment.created", "shipment.delivered"]
}' \
https://app.costdock.com/api/v1/webhooksResponse
{
"success": true,
"data": {
"id": "uuid",
"url": "https://example.com/webhooks/costdock",
"enabled": true,
"events": ["shipment.created", "shipment.delivered"],
"signing_key": "whsec_...",
"created_at": "2026-02-20T00:00:00Z"
}
}/api/v1/webhooks/:idGet a specific webhook endpoint including its signing key.
Request
curl -H "Authorization: Bearer sk_live_..." \
https://app.costdock.com/api/v1/webhooks/uuid-hereResponse
{
"success": true,
"data": {
"id": "uuid",
"url": "https://example.com/webhooks/costdock",
"enabled": true,
"events": ["shipment.created"],
"signing_key": "whsec_...",
"created_at": "2026-02-20T00:00:00Z",
"updated_at": "2026-02-20T00:00:00Z"
}
}/api/v1/webhooks/:idUpdate a webhook endpoint. All fields are optional.
Request
curl -X PATCH \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{"enabled": false}' \
https://app.costdock.com/api/v1/webhooks/uuid-hereResponse
{
"success": true,
"data": {
"id": "uuid",
"url": "https://example.com/webhooks/costdock",
"enabled": false,
"events": ["shipment.created"],
"created_at": "2026-02-20T00:00:00Z",
"updated_at": "2026-02-20T01:00:00Z"
}
}/api/v1/webhooks/:idDelete a webhook endpoint. This stops all event delivery to this URL.
Request
curl -X DELETE \
-H "Authorization: Bearer sk_live_..." \
https://app.costdock.com/api/v1/webhooks/uuid-hereResponse
{
"success": true,
"data": {
"deleted": true
}
}Signature Verification
Every webhook request includes signature headers that you should verify to ensure the payload was sent by CostDock and not tampered with. Use the svix SDK for easy verification.
Node.js
import { Webhook } from "svix";
const wh = new Webhook(process.env.COSTDOCK_WEBHOOK_SIGNING_KEY);
app.post("/webhooks/costdock", (req, res) => {
try {
const payload = wh.verify(req.body, {
"svix-id": req.headers["svix-id"],
"svix-timestamp": req.headers["svix-timestamp"],
"svix-signature": req.headers["svix-signature"],
});
// payload is verified — process it
console.log("Event type:", payload.type);
res.status(200).json({ received: true });
} catch (err) {
console.error("Webhook verification failed:", err);
res.status(400).json({ error: "Invalid signature" });
}
});Python
from svix.webhooks import Webhook
import os
wh = Webhook(os.environ["COSTDOCK_WEBHOOK_SIGNING_KEY"])
@app.route("/webhooks/costdock", methods=["POST"])
def handle_webhook():
try:
payload = wh.verify(request.get_data(), {
"svix-id": request.headers.get("svix-id"),
"svix-timestamp": request.headers.get("svix-timestamp"),
"svix-signature": request.headers.get("svix-signature"),
})
# payload is verified — process it
print("Event type:", payload["type"])
return {"received": True}, 200
except Exception as e:
print(f"Webhook verification failed: {e}")
return {"error": "Invalid signature"}, 400Important: Always verify webhook signatures before processing events. Responding with 2xx without verification opens your endpoint to spoofed events.
Retry Policy
Webhooks are delivered via Svix with automatic retries. If your endpoint returns a non-2xx status code or times out, the delivery will be retried with exponential backoff.
| Attempt | Delay |
|---|---|
| 1st retry | ~5 seconds |
| 2nd retry | ~5 minutes |
| 3rd retry | ~30 minutes |
| 4th retry | ~2 hours |
| 5th retry | ~8 hours |
Reliability: Failed deliveries are retried approximately 6 times over ~8 hours. After all retries are exhausted, the endpoint is automatically disabled.
Data Reference
Reference values you'll encounter in API responses and webhook payloads. Use these to build switch statements, filters, and routing logic in your integration.
Address Fields
The from and to objects share this schema. Either name or company is required.
| Field | Required | Format | Notes |
|---|---|---|---|
| name | Yes* | String (max 100) | Recipient or sender full name |
| company | No* | String (max 100) | Company name — can substitute for name |
| street1 | Yes | String (max 200) | Primary street address |
| street2 | No | String (max 200) | Apt, suite, unit, floor |
| city | Yes | String (max 100) | City |
| state | Yes | 2-letter code | US state abbreviation (e.g. CA, NY) |
| zip | Yes | 5-10 chars | ZIP or ZIP+4 (e.g. 94102, 94102-1234) |
| country | Yes | 2-letter code | Country code (US) |
| phone | No | String (max 20) | Phone number |
* At least one of name or company must be provided.
Parcel Fields
The parcel object describes the package being shipped. Used in rates, shipments, and batch endpoints.
| Field | Required | Valid Values | Default | Notes |
|---|---|---|---|---|
| packageType | No | packageenvelope | package | envelope uses standard flat dims (11x6x0.25") |
| weightLbs | Yes* | Number (max 150) | — | Weight in pounds (recommended) |
| weightOz | Yes* | Number (max 2400) | — | Weight in ounces — backwards compatible, takes precedence if both provided |
| lengthIn | If package | Number (max 108) | — | Length in inches |
| widthIn | If package | Number (max 108) | — | Width in inches |
| heightIn | If package | Number (max 108) | — | Height in inches |
| deliveryOption | No | nonesignatureadult_signature | none | Signature confirmation level |
| labelFormat | No | PNGPDFZPL | PNG | ZPL for thermal printers |
* Provide weightLbs (recommended) or weightOz (at least one required). Dimensions are required for package type — UPS and FedEx won't return rates without them.
Carriers & Services
The carrier and service fields appear in shipment responses, rate quotes, and webhook payloads. Values come from EasyPost and depend on which carrier accounts are connected.
| Carrier | Common Services | Void Window |
|---|---|---|
| USPS | PriorityExpressFirstGroundAdvantageParcelSelect | 30 days |
| UPS | Ground3DaySelect2ndDayAirNextDayAirSaverNextDayAir | 90 days |
| FedEx | GROUND_HOME_DELIVERYFEDEX_EXPRESS_SAVERFEDEX_2_DAYPRIORITY_OVERNIGHTSTANDARD_OVERNIGHT | 90 days |
Note: Exact service names depend on carrier configuration. Use the POST /api/v1/rates endpoint to discover available services for a specific origin/destination pair.
Shipment Statuses
The status field on shipments progresses through these values:
| Status | Description | Terminal? |
|---|---|---|
| quoted | Rate quote generated, label not yet purchased | No |
| purchased | Label purchased, awaiting carrier pickup | No |
| in_transit | Package picked up and moving through carrier network | No |
| delivered | Package delivered to recipient | Yes |
| voided | Label voided and credits refunded | Yes |
| returned | Package returned to sender | Yes |
| rejected | Label rejected by admin | Yes |
| failed | Label purchase or delivery failed | Yes |
Error Codes
| Code | Status | Description |
|---|---|---|
| UNAUTHORIZED | 401 | Invalid or missing API key |
| INVALID_REQUEST | 400/422 | Request validation failed (422 for address validation errors) |
| INSUFFICIENT_CREDITS | 402 | Not enough credits for this operation |
| AUTOPAY_FAILED | 403 | Autopay payment failed, add credits manually |
| NOT_FOUND | 404 | Resource not found |
| CONFLICT | 409 | Duplicate request or idempotency conflict |
| PAYLOAD_TOO_LARGE | 413 | Request body exceeds size limit |
| LIMIT_EXCEEDED | 422 | Resource limit reached (e.g. max webhooks) |
| RATE_LIMIT_EXCEEDED | 429 | Too many requests — slow down |
| RATE_EXPIRED | 400 | Rate quote has expired, refresh rates |
| EASYPOST_ERROR | 400 | Carrier returned an error |
| INTERNAL_ERROR | 500 | Unexpected server error — contact support |