Developers

Dropzon Public API

Integrate DZ Address lookup, Dropzon location search, package tracking, “Sign in with Dropzon” OAuth, and more into your applications. Built for e-commerce platforms, logistics companies, and developers.

Version:v1Stable

Quick Start

1

Get your API key

Sign in to the Developer Console and create an API key. Any Dropzon account can access the console.

2

Authenticate your requests

Include your API key in every request header.

curl -H "Authorization: Bearer YOUR_API_KEY" \
     "https://api.dropzon.app/api/v1/addresses/lookup?code=DZ%23001-01"
3

Start building

Explore the endpoints below. All responses are JSON. Rate limit: 100 requests/minute per key.

Authentication

All API requests require authentication using a Bearer token in the Authorization header.

Authorization: Bearer YOUR_API_KEY

Rate Limit

100 requests / minute

Per API key. Burst-friendly with sliding window.

Response Format

JSON (application/json)

All responses include a success boolean.

Error Handling

Errors follow a consistent format with HTTP status codes and descriptive messages.

{
  "success": false,
  "error": "Invalid or expired API key"
}
StatusCodeDescription
400BAD_REQUESTMissing or invalid parameters
401AUTH_INVALIDInvalid or missing API key
403FORBIDDENInsufficient permissions for this resource
404NOT_FOUNDResource not found
429RATE_LIMITEDToo many requests - slow down
500INTERNAL_ERRORSomething went wrong on our end

Addresses

Look up, search, and validate DZ Addresses. Search by DZ# code, full DZ code, or full address string. You can pass the query as a query parameter (?q=...) or directly in the URL path (/search/DZ%23001-01). Ideal for e-commerce checkout integrations.

Dropzons

Search for Dropzon locations, look up full address details for e-commerce checkout, and find the nearest pickup points.

Tracking

Track packages by tracking number and get real-time status updates for shipments.

Locations

Access the Dropzon location hierarchy - countries, regions, cities, and municipalities where Dropzon operates.

Shipping Rates

Get shipping rate estimates between DZ addresses. Calculate costs for in-city, domestic, cross-border, and international shipments.

Dispatch

Create and manage package dispatches via the API. Send single parcels or bulk dispatch up to 50 at once. Requires the "dispatch" scope on your API key.

Webhooks

Register webhook URLs to receive real-time notifications when package statuses change or deliveries are completed.

Sign in with Dropzon

OAuth 2.0 Authorization Code with PKCE

Overview

“Sign in with Dropzon” lets your users authenticate using their Dropzon account. After authorization, your app receives an access token that can be used to read the user's profile, email, phone, and DZ Address information - depending on the scopes you request.

The implementation follows the OAuth 2.0 Authorization Code flow with optional PKCE (Proof Key for Code Exchange) for public clients like mobile apps and SPAs.

Base URL:https://api.dropzon.app/api/oauth
Grant type:authorization_code

Setup

1

Create an OAuth app

Go to the Developer Console → OAuth Apps and create a new OAuth application.

2

Save your credentials

Copy your client_id (starts with dzo_) and client_secret (starts with dzs_). The secret is only shown once at creation time.

3

Register redirect URIs

Add one or more callback URLs where users will be redirected after authorization. Must be HTTPS in production (http://localhost is allowed for development).

Available Scopes

Request only the scopes your application needs. Users will see a list of requested permissions on the consent screen.

ScopeDescription
profileName and avatar
emailEmail address
phonePhone number
addressPrimary DZ Address
addressesAll DZ Addresses
identityVerified identity info
kyc_statusVerification status

Authorization Flow

1

Redirect user to Dropzon

Redirect the user's browser to the Dropzon authorization page with your app's parameters.

GET https://dropzon.app/oauth/authorize?
  client_id=dzo_your_client_id
  &redirect_uri=https://yourapp.com/callback
  &response_type=code
  &scope=profile email
  &state=random_csrf_token
  &code_challenge=S256_hash_of_verifier   # optional, for PKCE
  &code_challenge_method=S256              # optional, for PKCE
ParameterDescription
client_idYour OAuth app client ID (dzo_ prefix)
redirect_uriMust match a registered redirect URI
response_typeMust be "code"
scopeSpace-separated scopes (defaults to "profile")
stateRandom string to prevent CSRF attacks
code_challengePKCE challenge (S256 hash of code_verifier)
code_challenge_methodMust be "S256" when using PKCE
2

User signs in and approves

The user sees a consent screen showing your app name, description, and the permissions you're requesting. If they're not logged in, they'll sign in first. After approving, Dropzon redirects them back to your redirect_uri with an authorization code.

3

Handle the callback

Dropzon redirects the user back to your app with a code and state parameter. Verify the state matches what you sent.

https://yourapp.com/callback?code=AUTH_CODE_HERE&state=random_csrf_token

If the user denies access, you'll receive ?error=access_denied&state=... instead. Always check for the error parameter first.

4

Exchange code for tokens

Make a server-side POST request to exchange the authorization code for an access token and refresh token. This request must include your client_secret - never expose it in frontend code.

curl -X POST https://api.dropzon.app/api/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "authorization_code",
    "code": "AUTH_CODE_HERE",
    "redirect_uri": "https://yourapp.com/callback",
    "client_id": "dzo_your_client_id",
    "client_secret": "dzs_your_client_secret",
    "code_verifier": "original_pkce_verifier"  // only if using PKCE
  }'

Response

{
  "success": true,
  "data": {
    "access_token": "dza_...",
    "refresh_token": "dzr_...",
    "token_type": "Bearer",
    "expires_in": 3600,
    "scope": "profile email"
  }
}
5

Get user info

Use the access token to fetch the authenticated user's profile. The fields returned depend on the scopes granted by the user.

curl https://api.dropzon.app/api/oauth/userinfo \
  -H "Authorization: Bearer dza_your_access_token"

Fields returned per scope

alwayssub - the user's unique Dropzon ID
profilename, avatar_url, role
emailemail, email_verified
phonephone, phone_verified
addressaddress - the user's primary DZ Address with full Dropzon location details
addressesaddresses - array of all the user's DZ Addresses
identityidentity - date of birth, gender, nationality, ID document type/country
kyc_statuskyc - verification level, status, verified_at, email/phone verified

Example response (profile + email scopes only)

{
  "success": true,
  "data": {
    "sub": "c25afbbd-73d8-465b-91e6-4567c0f2aed2",
    "name": "Musa Lawi",
    "avatar_url": null,
    "role": "customer",
    "email": "musa@example.com",
    "email_verified": true
  }
}

Full response (all scopes granted)

{
  "success": true,
  "data": {
    "sub": "c25afbbd-73d8-465b-91e6-4567c0f2aed2",
    "name": "Musa Lawi",
    "avatar_url": null,
    "role": "customer",
    "email": "musa@example.com",
    "email_verified": true,
    "phone": "+255621451238",
    "phone_verified": false,
    "address": {
      "dz_code": "DZ#001-01",
      "dz_full_code": "DZ-001-001-001-003-001",
      "label": "Crib",
      "status": "active",
      "is_primary": true,
      "dropzon": {
        "id": "a1b2c3d4-...",
        "name": "Sinza C Dropzon Hub",
        "short_code": "DZ#001",
        "full_code": "DZ-001-001-001-003-001",
        "type": "branch",
        "status": "active",
        "house_no": "21",
        "street": "Manet St",
        "street_alias": "Gandabahari Rd",
        "ward": "Sinza C",
        "district": "Ubungo",
        "state": "Dar es Salaam",
        "zip_code": "16102",
        "gps": {
          "lat": -6.781463,
          "lng": 39.221159
        },
        "plus_code": "669C+CF7 Dar es Salaam",
        "municipality": "Ubungo",
        "city": "Dar es Salaam",
        "region": "Dar es Salaam",
        "country": "Tanzania",
        "country_code": "TZ",
        "phone_prefix": "+255"
      },
      "formatted": {
        "full": "21 Manet St, DZ#001-01, Dar es Salaam, 16102 TZ",
        "line1": "21 Manet St",
        "line2": "DZ#001-01",
        "city": "Dar es Salaam",
        "region": "Dar es Salaam",
        "municipality": "Ubungo",
        "country": "Tanzania",
        "country_code": "TZ",
        "zip_code": "16102",
        "postal_code": "16102"
      }
    },
    "addresses": [
      "...array of address objects (same structure as above)"
    ],
    "identity": {
      "date_of_birth": "1995-08-22",
      "gender": "male",
      "nationality": "TZ",
      "id_document_type": "national_id",
      "id_document_country": "TZ"
    },
    "kyc": {
      "level": "standard",
      "status": "verified",
      "verified_at": "2025-03-15T10:30:00.000Z",
      "email_verified": true,
      "phone_verified": true
    }
  }
}

Token Management

Token Lifetimes

Access Token

1 hour

Prefix: dza_

Refresh Token

30 days

Prefix: dzr_

Refreshing Tokens

When the access token expires, use the refresh token to get a new pair. The old refresh token is rotated (invalidated) and a new one is returned.

curl -X POST https://api.dropzon.app/api/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "refresh_token",
    "refresh_token": "dzr_your_refresh_token",
    "client_id": "dzo_your_client_id",
    "client_secret": "dzs_your_client_secret"
  }'

Revoking Tokens

Revoke an access or refresh token when the user logs out of your app. Per RFC 7009, this endpoint always returns 200.

curl -X POST https://api.dropzon.app/api/oauth/revoke \
  -H "Content-Type: application/json" \
  -d '{
    "token": "dza_or_dzr_token_here",
    "client_id": "dzo_your_client_id",
    "client_secret": "dzs_your_client_secret"
  }'

Complete Example- Node.js / Express

const express = require('express');
const crypto = require('crypto');
const app = express();

const CLIENT_ID = 'dzo_your_client_id';
const CLIENT_SECRET = 'dzs_your_client_secret';
const REDIRECT_URI = 'https://yourapp.com/callback';
const OAUTH_AUTHORIZE = 'https://dropzon.app/oauth';  // consent screen (frontend)
const OAUTH_API = 'https://api.dropzon.app/api/oauth';              // token + userinfo (API)

// Step 1: Redirect to Dropzon
app.get('/login', (req, res) => {
  const state = crypto.randomBytes(16).toString('hex');
  req.session.oauthState = state;

  const params = new URLSearchParams({
    client_id: CLIENT_ID,
    redirect_uri: REDIRECT_URI,
    response_type: 'code',
    scope: 'profile email',
    state,
  });

  res.redirect(OAUTH_AUTHORIZE + '/authorize?' + params);
});

// Step 3-4: Handle callback and exchange code
app.get('/callback', async (req, res) => {
  const { code, state, error } = req.query;

  if (error) return res.send('Authorization denied');
  if (state !== req.session.oauthState) return res.status(403).send('Invalid state');

  // Exchange code for tokens
  const tokenRes = await fetch(OAUTH_API + '/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      grant_type: 'authorization_code',
      code,
      redirect_uri: REDIRECT_URI,
      client_id: CLIENT_ID,
      client_secret: CLIENT_SECRET,
    }),
  });
  const { data: tokens } = await tokenRes.json();

  // Step 5: Fetch user info
  const userRes = await fetch(OAUTH_API + '/userinfo', {
    headers: { Authorization: 'Bearer ' + tokens.access_token },
  });
  const { data: user } = await userRes.json();

  // user = { sub, name, email, ... }
  req.session.user = user;
  res.redirect('/dashboard');
});

Endpoint Reference

MethodEndpointAuth
GET/oauth/authorizeNone
POST/oauth/tokenNone
GET/oauth/userinfoBearer (access token)
POST/oauth/revokeNone

Security Best Practices

Always validate the state parameter

Compare the state in the callback with the one you sent to prevent CSRF attacks.

Keep client_secret server-side

Never expose your client secret in frontend JavaScript, mobile app binaries, or version control.

Use PKCE for public clients

Single-page apps and mobile apps should use PKCE (code_challenge + code_verifier) since they cannot securely store a client_secret.

Use HTTPS redirect URIs

Always use HTTPS for redirect URIs in production. HTTP is only allowed for localhost during development.

Request minimal scopes

Only request the scopes your application actually needs. Users are more likely to approve fewer permissions.

Handle token expiry gracefully

Use the refresh token flow to renew access tokens. If the refresh token is also expired, redirect the user to re-authorize.

KYC-Lite Identity Verification

Dropzon verifies user identities as part of its address infrastructure. With the KYC-lite scopes, your app can check a user's verification level or request verified identity data - without building your own KYC pipeline.

How it works

1

User signs up on Dropzon

When a user creates a Dropzon account and registers a DZ Address, they go through identity verification steps.

2

Verification levels increase

Email + phone verification gives "basic" level. Government ID submission upgrades to "standard". ID + selfie match reaches "enhanced".

3

Your app requests KYC scopes

Add identity and/or kyc_status to your OAuth scope parameter. The user sees what you are requesting on the consent screen.

4

Read verification data

After authorization, call GET /oauth/userinfo. The response includes the user's verification level and/or identity details based on granted scopes.

Verification Levels

LevelRequirements
noneAccount created, no verification
basicEmail and phone verified
standardGovernment-issued ID submitted and approved
enhancedGovernment ID + selfie verification match

KYC-Lite Scopes

kyc_statusRecommended

Returns the user's verification level and status without revealing any personal data. Ideal for age gates, trust tiers, and risk decisions.

// GET /oauth/userinfo (with kyc_status scope)
{
  "sub": "usr_abc123",
  "kyc": {
    "level": "standard",        // none | basic | standard | enhanced
    "status": "verified",       // none | pending | verified | rejected | expired
    "verified_at": "2025-03-15T10:30:00Z",
    "email_verified": true,
    "phone_verified": true
  }
}
identitySensitive

Returns verified personal identity information. Users will see a clear warning on the consent screen. Only request this scope if your application legally requires identity data (e.g. financial services).

// GET /oauth/userinfo (with identity scope)
{
  "sub": "usr_abc123",
  "identity": {
    "date_of_birth": "1995-08-22",
    "gender": "male",
    "nationality": "TZ",
    "id_document_type": "national_id",    // national_id | passport | driving_license
    "id_document_country": "TZ"
  }
}

Common Use Cases

Age verification

Check if a user is 18+ using kyc_status level without seeing their date of birth.

scope: kyc_status

Trust tiers

Offer higher transaction limits to users with "standard" or "enhanced" verification.

scope: kyc_status

Financial compliance

Retrieve government ID details for users of financial or insurance products.

scope: identity

Delivery confirmation

Combine address + kyc_status scopes to verify both the delivery address and recipient identity.

scope: identity + address

Example: Request KYC Status

GET https://api.dropzon.app/api/oauth/authorize?
  client_id=dzo_your_client_id
  &redirect_uri=https://yourapp.com/callback
  &response_type=code
  &scope=profile kyc_status
  &state=random_csrf_token

The user will see a consent screen asking to share their name, avatar, and identity verification status. No personal identity documents or dates are shared with the kyc_status scope.

Sign in with Dropzon - Button

Add a branded “Sign in with Dropzon” button to your app. Pick a variant and size below, then copy the code.

Variant

Size

<a href="https://dropzon.app/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&response_type=code&scope=profile email&state=RANDOM_STATE"
   style="display:inline-flex;align-items:center;gap:10px;height:44px;padding:0 20px;background:#0d84d4;color:#fff;border:none;border-radius:12px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;font-size:14px;font-weight:600;text-decoration:none;cursor:pointer;transition:opacity 0.15s;"
   onmouseover="this.style.opacity='0.9'" onmouseout="this.style.opacity='1'">
  <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z" fill="#ffffff"/><circle cx="12" cy="9" r="2.5" fill="white"/></svg>
  Sign in with Dropzon
</a>

Branding Guidelines

Use the provided Dropzon pin icon next to the text

Use "Sign in with Dropzon" as the button label

Match one of the four official variants above

Do not stretch, skew, or modify the pin icon

Do not change the button text to other wording

Do not use a font size smaller than 14px

Checkout Widget

Embeddable DZ Address autofill for e-commerce checkout pages

Overview

The Checkout Widget is a drop-in JavaScript component that adds a DZ# code input to your checkout page. When a customer enters their DZ# code (e.g. DZ#001-01), the widget validates it in real time and auto-fills their full address into your existing form fields.

No framework dependencies required - works with any website, from plain HTML to React, Vue, or Shopify. The widget handles input formatting, debounced API calls, error states, and a polished verified-address display.

Script URL:https://api.dropzon.app/widget/dz-address.js
Version:1.0.0Stable

Quick Start

Add two lines to your checkout page and the widget handles the rest.

<!-- 1. Add a target div where the widget should appear -->
<div id="dz-address"></div>

<!-- 2. Include the widget script with your API key -->
<script
  src="https://api.dropzon.app/widget/dz-address.js"
  data-api-key="YOUR_API_KEY"
  data-target="#dz-address"
></script>

The widget automatically initializes when the page loads. It reads configuration fromdata-*attributes on the script tag.

Auto-Fill Checkout Fields

Point the widget at your existing form fields using data-autofill-* attributes. When a DZ# code resolves successfully, the widget writes the address components directly into your form inputs.

<div id="dz-address"></div>

<!-- Your existing checkout form -->
<input id="address-line1" name="line1" placeholder="Address line 1" />
<input id="address-line2" name="line2" placeholder="Address line 2" />
<input id="address-city"  name="city"  placeholder="City" />
<input id="address-zip"   name="zip"   placeholder="ZIP / Postal code" />
<input id="address-country" name="country" placeholder="Country" />

<script
  src="https://api.dropzon.app/widget/dz-address.js"
  data-api-key="YOUR_API_KEY"
  data-target="#dz-address"
  data-autofill-line1="#address-line1"
  data-autofill-line2="#address-line2"
  data-autofill-city="#address-city"
  data-autofill-zip="#address-zip"
  data-autofill-country="#address-country"
></script>

Programmatic Usage

For full control, initialize the widget via JavaScript instead of data attributes. This gives you access to event callbacks and programmatic methods.

<script src="https://api.dropzon.app/widget/dz-address.js"></script>
<script>
  const widget = Dropzon.init({
    apiKey: 'YOUR_API_KEY',
    target: '#dz-address',
    label: 'Delivery Address',
    placeholder: 'Enter your DZ# code',

    // Auto-fill form fields (CSS selectors)
    autofill: {
      line1: '#checkout-line1',
      line2: '#checkout-line2',
      city: '#checkout-city',
      zip: '#checkout-zip',
      country: '#checkout-country',
    },

    // Callbacks
    onResolve: function(address) {
      console.log('Address resolved:', address);
      // address.formatted.full   - "21 Manet St, DZ#001-01, Dar es Salaam, 16102, TZ"
      // address.formatted.line1  - "21 Manet St"
      // address.formatted.line2  - "DZ#001-01"
      // address.formatted.city   - "Dar es Salaam"
      // address.formatted.zip_code - "16102"
      // address.formatted.country - "Tanzania"
      // address.dropzon.name     - "Goba Road Dropzon"
    },

    onError: function(err) {
      console.log('Lookup failed:', err.error);
    },

    onClear: function() {
      console.log('Address cleared');
    },
  });

  // Programmatic methods
  widget.getCode();       // Returns current input value
  widget.getValue();      // Returns resolved address or null
  widget.setCode('DZ#001-01');  // Set a code and trigger lookup
  widget.clear();         // Clear everything
  widget.destroy();       // Remove widget from DOM
</script>

Headless API (No Widget UI)

If you only need the DZ# lookup logic without the widget UI, use the static helper methods directly. These return Promises and work well with async/await.

<script src="https://api.dropzon.app/widget/dz-address.js"></script>
<script>
  // Look up a DZ# code - returns full address data
  Dropzon.lookup('YOUR_API_KEY', 'DZ#001-01')
    .then(function(address) {
      console.log(address.formatted.full);
    })
    .catch(function(err) {
      console.error(err);
    });

  // Validate a DZ# code - check if it exists and is active
  Dropzon.validate('YOUR_API_KEY', 'DZ#001-01')
    .then(function(result) {
      console.log('Valid:', result.valid);
    });
</script>

Configuration Reference

Script Tag Attributes

AttributeDescription
data-api-keyYour Dropzon API key (dzk_...)
data-targetCSS selector for the container element. Default: #dz-address
data-labelLabel text above the input. Default: "DZ Address"
data-placeholderInput placeholder text. Default: "Enter DZ# code (e.g. DZ#001-01)"
data-show-resultShow resolved address card below input. Default: true
data-show-poweredShow "Powered by Dropzon" badge. Default: true
data-autofill-line1CSS selector for the address line 1 input
data-autofill-line2CSS selector for the address line 2 input
data-autofill-cityCSS selector for the city input
data-autofill-stateCSS selector for the state/region input
data-autofill-zipCSS selector for the ZIP/postal code input
data-autofill-countryCSS selector for the country input
data-autofill-country-codeCSS selector for the country code input (ISO 2-letter)

JavaScript Init Options

OptionDescription
apiKeyYour Dropzon API key (required)
targetCSS selector or DOM element for the container (required)
labelLabel text, or false to hide. Default: "DZ Address"
placeholderInput placeholder text
showResultShow verified address card. Default: true
showPoweredShow "Powered by Dropzon". Default: true
autofillMap of field names to CSS selectors: { line1, line2, city, state, zip, country, country_code }
onResolveCalled when a DZ# code resolves to an address. Receives address data object.
onErrorCalled when a lookup fails. Receives { code, error }.
onClearCalled when the input is cleared.
onChangeCalled on every input change. Receives the current value.

Resolved Address Shape

The onResolve callback (and Dropzon.lookup()) return the same address object as the GET /api/v1/addresses/lookup endpoint. See the Addresses section above for the full response shape.

{
  "dz_code": "DZ#001-01",
  "dz_full_code": "DZ-001-001-001-001-001",
  "label": "Home",
  "status": "active",
  "formatted": {
    "full": "21 Manet St, DZ#001-01, Dar es Salaam, 16102, TZ",
    "line1": "21 Manet St",
    "line2": "DZ#001-01",
    "city": "Dar es Salaam",
    "region": "Dar es Salaam",
    "zip_code": "16102",
    "postal_code": "16102",
    "country": "Tanzania",
    "country_code": "TZ"
  },
  "dropzon": {
    "name": "Goba Road Dropzon",
    "dz_code": "DZ#001",
    "city": "Dar es Salaam"
  }
}

How It Works

1

Customer enters DZ# code

The widget shows a styled input field. As the customer types their DZ# code (e.g. DZ#001-01), the widget validates the format in real time.

2

Widget calls Dropzon API

Once a valid DZ# code format is detected (including the slot number after the dash), the widget makes a debounced API call to /api/v1/addresses/lookup using your API key.

3

Address resolves

The API returns the full address - street, city, ZIP code, country, and the associated Dropzon location. The widget displays a verified address card with all details.

4

Form fields auto-fill

If you configured autofill selectors, the widget writes each address component into your existing form inputs and triggers input/change events so your framework picks up the values.

Verification Badge

Embeddable trust seal showing a DZ address is verified

Overview

The Verification Badge is a lightweight embeddable seal that confirms a DZ address is valid and active on the Dropzon network. Display it on order confirmations, receipts, invoices, or anywhere you want to show the customer that their delivery address has been verified.

The badge loads, calls the Dropzon validate API in the background, and renders a Verified, Not Verified, or Verifying... state with smooth animations. It supports 4 themes, 3 sizes, and hover tooltips.

Script URL:https://api.dropzon.app/widget/dz-badge.js
Version:1.0.0Stable

Quick Start

Add two lines to your order confirmation or receipt page.

<!-- 1. Add a target div where the badge should appear -->
<div id="dz-badge"></div>

<!-- 2. Include the badge script with your API key and the DZ# code -->
<script
  src="https://api.dropzon.app/widget/dz-badge.js"
  data-api-key="YOUR_API_KEY"
  data-code="DZ#001-01"
  data-target="#dz-badge"
></script>

The badge automatically verifies the code on load and displays the result. It transitions from a “Verifying...” spinner to either “DZ Verified” (green) or “Not Verified” (red).

Themes and Sizes

The badge ships with 4 built-in themes and 3 sizes. Set them viadata-theme anddata-size attributes.

<!-- Light theme (default), medium size -->
<script src="https://api.dropzon.app/widget/dz-badge.js"
  data-api-key="YOUR_API_KEY" data-code="DZ#001-01"
  data-target="#badge-1" data-theme="light" data-size="md">
</script>

<!-- Dark theme, large size -->
<script src="https://api.dropzon.app/widget/dz-badge.js"
  data-api-key="YOUR_API_KEY" data-code="DZ#001-01"
  data-target="#badge-2" data-theme="dark" data-size="lg">
</script>

<!-- Minimal theme (transparent bg), small size -->
<script src="https://api.dropzon.app/widget/dz-badge.js"
  data-api-key="YOUR_API_KEY" data-code="DZ#001-01"
  data-target="#badge-3" data-theme="minimal" data-size="sm">
</script>

<!-- Brand theme (Dropzon blue) -->
<script src="https://api.dropzon.app/widget/dz-badge.js"
  data-api-key="YOUR_API_KEY" data-code="DZ#001-01"
  data-target="#badge-4" data-theme="brand">
</script>

Light

Green verified on white - works on light backgrounds

data-theme="light"

Dark

Green verified on dark green - works on dark backgrounds

data-theme="dark"

Minimal

Transparent background with subtle border

data-theme="minimal"

Brand

Dropzon blue verified - matches the Dropzon brand

data-theme="brand"

Programmatic Usage

For full control, initialize the badge via JavaScript. This gives you access to event callbacks and methods like reverify().

<script src="https://api.dropzon.app/widget/dz-badge.js"></script>
<script>
  const badge = DropzonBadge.init({
    apiKey: 'YOUR_API_KEY',
    code: 'DZ#001-01',
    target: '#dz-badge',
    size: 'md',        // 'sm' | 'md' | 'lg'
    theme: 'light',    // 'light' | 'dark' | 'minimal' | 'brand'
    showCode: true,    // Show the DZ# code next to "Verified"
    showTooltip: true, // Hover tooltip with status message
    animated: true,    // Pop-in animation on verify

    onVerified: function(data) {
      console.log('Address verified:', data);
      // data.valid  = true
      // data.active = true
      // data.code   = 'DZ#001-01'
    },

    onFailed: function(err) {
      console.log('Verification failed:', err);
    },
  });

  // Methods
  badge.getStatus();  // 'pending' | 'verified' | 'unverified'
  badge.reverify();   // Re-check (e.g. after code change)
  badge.destroy();    // Remove badge from DOM
</script>

Headless Verify (No Badge UI)

If you only need the verification check without any UI, use the staticDropzonBadge.verify()method. It returns a Promise.

<script src="https://api.dropzon.app/widget/dz-badge.js"></script>
<script>
  DropzonBadge.verify('YOUR_API_KEY', 'DZ#001-01')
    .then(function(result) {
      if (result.valid && result.active) {
        console.log('Address is verified and active');
      } else {
        console.log('Address not verified');
      }
    })
    .catch(function(err) {
      console.error('Verification error:', err);
    });
</script>

Configuration Reference

Script Tag Attributes

AttributeDescription
data-api-keyYour Dropzon API key (dzk_...)
data-codeThe DZ# code to verify (e.g. DZ#001-01)
data-targetCSS selector for the container element. Default: #dz-badge
data-sizeBadge size: "sm", "md" (default), or "lg"
data-themeBadge theme: "light" (default), "dark", "minimal", or "brand"
data-show-codeShow the DZ# code in the badge. Default: true
data-show-tooltipShow hover tooltip with status message. Default: true
data-animatedEnable pop-in animation on verify. Default: true

JavaScript Init Options

OptionDescription
apiKeyYour Dropzon API key (required)
codeDZ# code to verify (required)
targetCSS selector or DOM element for the container (required)
size"sm", "md" (default), or "lg"
theme"light" (default), "dark", "minimal", or "brand"
showCodeShow the DZ# code in the badge. Default: true
showTooltipShow hover tooltip. Default: true
animatedPop-in animation on verify. Default: true
onVerifiedCalled when the address is verified. Receives { valid, active, code }.
onFailedCalled when verification fails. Receives { code, error }.

Common Use Cases

🛒

Order Confirmation Pages

After checkout, show the badge next to the shipping address to reassure the customer their DZ address is verified and the package will reach the right Dropzon.

📧

Email Receipts

Include a "DZ Verified" status line in order confirmation emails. Use the headless DropzonBadge.verify() API to check before sending.

📦

Shipping Labels

Validate the DZ# code before printing shipping labels to catch typos and inactive addresses early.

💻

Admin Dashboards

Display verification badges in your order management system so support staff can quickly see which orders have verified DZ addresses.

Address Embeds

Copy-paste HTML snippets to showcase a DZ Address on any website

Overview

Private Dropzon holders can embed their verified DZ Address directly on their own website - product pages, checkout confirmations, contact pages, invoices, and more.

Every embed links to the public address page at https://dropzon.app/address/DZ%23XXX-XX where anyone can verify the address is real and active on the Dropzon network. No script tags, no API keys, no dependencies - pure HTML with inline styles that works on any platform.

No API key required
Works on any website or email
Inline styles only - no CSS conflicts

Where to get your embed snippets

Each snippet is pre-generated with your exact address data. To get yours:

  1. 1Log in to your Dropzon account at dropzon.app/app
  2. 2Go to Private Dropzons and open your DZ
  3. 3Click the "Embed & Share" tab
  4. 4Pick a variant, preview it live, then click "Copy HTML" and paste it into your site

Embed Variants

Dark Badge

Compact inline badge. Great for footers and navigation bars.

<a href="https://dropzon.app/address/DZ%23007-01" target="_blank" rel="noopener noreferrer" style="display:inline-flex;align-items:center;gap:8px;background:#0f172a;color:#fff;padding:8px 14px;border-radius:8px;text-decoration:none;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;font-size:13px;line-height:1;">
  <img src="https://dropzon.app/email-logo.png" width="18" height="18" style="border-radius:4px;flex-shrink:0;" alt="Dropzon">
  <span style="font-weight:700;letter-spacing:-0.3px;">DZ#007-01</span>
  <span style="color:#94a3b8;font-size:11px;">Dar es Salaam · TZ</span>
</a>

Blue Badge

Brand-colored badge. Stands out on light backgrounds.

<a href="https://dropzon.app/address/DZ%23007-01" target="_blank" rel="noopener noreferrer" style="display:inline-flex;align-items:center;gap:8px;background:#0d84d4;color:#fff;padding:8px 14px;border-radius:8px;text-decoration:none;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;font-size:13px;line-height:1;">
  <img src="https://dropzon.app/email-logo.png" width="18" height="18" style="border-radius:4px;flex-shrink:0;" alt="Dropzon">
  <span style="font-weight:700;letter-spacing:-0.3px;">DZ#007-01</span>
  <span style="color:rgba(255,255,255,0.75);font-size:11px;">Dar es Salaam · TZ</span>
</a>

Light Badge

Subtle bordered badge. Works well inside cards and forms.

<a href="https://dropzon.app/address/DZ%23007-01" target="_blank" rel="noopener noreferrer" style="display:inline-flex;align-items:center;gap:8px;background:#fff;color:#0f172a;padding:8px 14px;border-radius:8px;border:1.5px solid #e2e8f0;text-decoration:none;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;font-size:13px;line-height:1;">
  <img src="https://dropzon.app/email-logo.png" width="18" height="18" style="border-radius:4px;flex-shrink:0;" alt="Dropzon">
  <span style="font-weight:700;letter-spacing:-0.3px;color:#0d84d4;">DZ#007-01</span>
  <span style="color:#64748b;font-size:11px;">Dar es Salaam · TZ</span>
</a>

Address Card (Light)

Full branded card with business name and complete address. Ideal for Contact Us pages.

<a href="https://dropzon.app/address/DZ%23007-01" target="_blank" rel="noopener noreferrer" style="display:block;max-width:280px;background:#fff;border:1.5px solid #e2e8f0;border-radius:14px;padding:16px 18px;text-decoration:none;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;">
  <div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid #f1f5f9;">
    <img src="https://dropzon.app/email-logo.png" width="22" height="22" style="border-radius:6px;flex-shrink:0;" alt="Dropzon">
    <span style="font-size:13px;font-weight:700;color:#0d84d4;">Drop<span style="font-weight:400;">zon</span></span>
    <span style="margin-left:auto;font-size:10px;font-weight:600;color:#16a34a;background:#f0fdf4;border:1px solid #bbf7d0;padding:2px 7px;border-radius:20px;">VERIFIED</span>
  </div>
  <p style="margin:0 0 2px;font-size:14px;font-weight:700;color:#0f172a;">Dropzon HQ</p>
  <p style="margin:0 0 1px;font-size:12px;color:#475569;">21 Manet St</p>
  <p style="margin:0 0 1px;font-size:12px;color:#0d84d4;font-weight:600;">DZ#007-01</p>
  <p style="margin:0 0 12px;font-size:12px;color:#475569;">Dar es Salaam, 16102 TZ</p>
  <p style="margin:0;font-size:11px;color:#94a3b8;">Tap to verify this address →</p>
</a>

Address Card (Dark)

Same card in dark theme. Matches dark-mode websites.

<a href="https://dropzon.app/address/DZ%23007-01" target="_blank" rel="noopener noreferrer" style="display:block;max-width:280px;background:#0f172a;border:1.5px solid #1e293b;border-radius:14px;padding:16px 18px;text-decoration:none;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;">
  <div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid #1e293b;">
    <img src="https://dropzon.app/email-logo.png" width="22" height="22" style="border-radius:6px;flex-shrink:0;" alt="Dropzon">
    <span style="font-size:13px;font-weight:700;color:#38bdf8;">Drop<span style="font-weight:400;color:#94a3b8;">zon</span></span>
    <span style="margin-left:auto;font-size:10px;font-weight:600;color:#4ade80;background:rgba(74,222,128,0.1);border:1px solid rgba(74,222,128,0.3);padding:2px 7px;border-radius:20px;">VERIFIED</span>
  </div>
  <p style="margin:0 0 2px;font-size:14px;font-weight:700;color:#f1f5f9;">Dropzon HQ</p>
  <p style="margin:0 0 1px;font-size:12px;color:#94a3b8;">21 Manet St</p>
  <p style="margin:0 0 1px;font-size:12px;color:#38bdf8;font-weight:600;">DZ#007-01</p>
  <p style="margin:0 0 12px;font-size:12px;color:#94a3b8;">Dar es Salaam, 16102 TZ</p>
  <p style="margin:0;font-size:11px;color:#475569;">Tap to verify this address →</p>
</a>

Inline Text Link

Drop a clickable address line inside any paragraph or block of text.

<a href="https://dropzon.app/address/DZ%23007-01" target="_blank" rel="noopener noreferrer" style="display:inline-flex;align-items:center;gap:6px;color:#0d84d4;text-decoration:none;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;font-size:14px;"><img src="https://dropzon.app/email-logo.png" width="16" height="16" style="border-radius:3px;flex-shrink:0;" alt="Dropzon">21 Manet St, DZ#007-01, Dar es Salaam, 16102 TZ</a>

Checkout / Shipping Form Fields

When filling out a shipping or checkout form on any platform (Amazon, Shopify, Jumia, Craught, etc.), use this field mapping. The DZ# code goes into Address Line 2 - most international forms have this field and it routes the package to the correct Dropzon automatically.

Form FieldValue to enter
Full Name / BusinessDropzon HQYour business or personal name
Address Line 121 Manet StStreet address from your DZ profile
Address Line 2DZ#007-01Your DZ# code - this is the key field
CityDar es Salaam
ZIP / Postal Code16102
CountryTZISO 2-letter country code
Why Address Line 2? The DZ# code is your unique Dropzon slot identifier. Putting it on its own line ensures it is never truncated or merged with the street address, and Dropzon agents can scan it directly when the package arrives.

Public Address Page

Every DZ# code has a public, SEO-indexed page that anyone can visit to verify the address is real. All embed snippets link to this page automatically.

https://dropzon.app/address/DZ%23007-01

# URL pattern
https://dropzon.app/address/{url-encoded-dz-code}

# Examples
https://dropzon.app/address/DZ%23001-01   → DZ#001-01
https://dropzon.app/address/DZ%23007-03   → DZ#007-03

The page shows the Dropzon name, full address, GPS location, operating hours, and the active/inactive status. It is server-rendered and fully indexable by search engines - useful for SEO when you link your shipping address from your website.

Webhook Integration Guide

Real-time delivery notifications via HMAC-signed HTTP callbacks

Overview

Webhooks let your application receive real-time notifications when package events occur - such as a package being received, dispatched, delivered, or collected. Instead of polling the tracking API, you register an HTTPS endpoint and Dropzon pushes event payloads to it automatically.

Every webhook delivery is signed with HMAC-SHA256 using your webhook secret, so you can verify that requests genuinely come from Dropzon. Failed deliveries are retried with exponential backoff, and delivery history is logged for debugging.

Signing:HMAC-SHA256
Retries:3 attempts
Timeout:10 seconds
Events:11 event types

How It Works

1

Register a webhook

POST to /api/v1/webhooks with your HTTPS endpoint URL and the event types you want to subscribe to. Your API key must have the "webhooks" scope.

2

Dropzon signs and sends

When a matching event occurs, Dropzon builds a JSON payload, signs it with HMAC-SHA256 using your webhook secret, and POSTs it to your URL.

3

You verify and respond

Your server verifies the signature, processes the event, and responds with a 2xx status code within 10 seconds.

4

Retries on failure

If your endpoint returns a non-2xx status or times out, Dropzon retries up to 3 times with exponential backoff (2s, 4s, 8s delays).

Payload Format

Every webhook delivery sends a JSON payload wrapped in a standard envelope. The top-level fields are always present. The data object contains the event-specific information (package details, tracking info, etc.).

{
  "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "event": "package.delivered",
  "createdAt": "2025-01-15T14:30:00.000Z",
  "data": {
    "package_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "tracking_number": "DZP-KE-20250115-ABC123",
    "status": "delivered",
    "origin_code": "DZ#001-01",
    "destination_code": "DZ#002-05",
    "sender_name": "John Doe",
    "recipient_name": "Jane Smith",
    "delivered_at": "2025-01-15T14:28:00.000Z"
  }
}
FieldDescription
idUnique delivery ID - use this for idempotency checks
eventThe event type that triggered this delivery (e.g. "package.delivered")
createdAtTimestamp when the event was created
dataEvent-specific payload with package or tracking details

Event Types

Subscribe to specific events when registering your webhook. You can subscribe to all 11 events or pick only the ones relevant to your integration.

EventDescription
package.receivedPackage has been received at the origin Dropzon location
package.at_originPackage is at the origin hub, awaiting dispatch
package.in_transitPackage has been dispatched and is in transit to the destination
package.at_destinationPackage has arrived at the destination Dropzon location
package.ready_for_pickupPackage is ready for the recipient to pick up
package.out_for_deliveryPackage is out for last-mile delivery to the recipient
package.deliveredPackage has been delivered to the recipient
package.collectedPackage has been collected by the recipient from the Dropzon
package.returnedPackage has been returned to the sender
package.lostPackage has been marked as lost
tracking.updatedGeneral tracking update (weighed, payment confirmed, customs hold/cleared, arrived at hub)

HTTP Headers

Each webhook delivery includes these headers. Use them to identify the event, verify the signature, and correlate deliveries.

HeaderDescription
Content-TypeAlways application/json
User-AgentAlways Dropzon-Webhooks/1.0 - use this to identify webhook requests
X-Dropzon-EventThe event type (e.g. package.delivered)
X-Dropzon-DeliveryUnique delivery ID (UUID) matching the payload id field
X-Dropzon-SignatureHMAC-SHA256 signature in the format sha256=<hex_digest>

Signature Verification

Always verify the X-Dropzon-Signature header to confirm the request comes from Dropzon and has not been tampered with. The signature is computed as sha256=HMAC-SHA256(raw_body, your_webhook_secret).

Node.js / Express

const crypto = require('crypto');

function verifyWebhookSignature(req, secret) {
  const signature = req.headers['x-dropzon-signature'];
  if (!signature) return false;

  const body = JSON.stringify(req.body);
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(body)
    .digest('hex');

  // Use timingSafeEqual to prevent timing attacks
  try {
    return crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(expected)
    );
  } catch {
    return false;
  }
}

// Express middleware example
app.post('/webhooks/dropzon', express.json(), (req, res) => {
  const WEBHOOK_SECRET = process.env.DROPZON_WEBHOOK_SECRET;

  if (!verifyWebhookSignature(req, WEBHOOK_SECRET)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const { id, event, data } = req.body;

  switch (event) {
    case 'package.delivered':
      console.log('Package delivered:', data.tracking_number);
      // Update your order status, send customer notification, etc.
      break;
    case 'package.in_transit':
      console.log('Package in transit:', data.tracking_number);
      break;
    // Handle other events...
  }

  // Respond quickly with 200
  res.status(200).json({ received: true });
});

Python / Flask

import hmac
import hashlib
from flask import Flask, request, jsonify

app = Flask(__name__)
WEBHOOK_SECRET = 'your_webhook_secret'

def verify_signature(payload, signature, secret):
    if not signature:
        return False
    expected = 'sha256=' + hmac.new(
        secret.encode('utf-8'),
        payload,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)

@app.route('/webhooks/dropzon', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-Dropzon-Signature')
    if not verify_signature(request.data, signature, WEBHOOK_SECRET):
        return jsonify({'error': 'Invalid signature'}), 401

    event = request.json
    event_type = event.get('event')
    data = event.get('data', {})

    if event_type == 'package.delivered':
        print(f"Package delivered: {data.get('tracking_number')}")
        # Update order status, notify customer, etc.

    return jsonify({'received': True}), 200

PHP

<?php
$webhookSecret = getenv('DROPZON_WEBHOOK_SECRET');
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_DROPZON_SIGNATURE'] ?? '';

$expected = 'sha256=' . hash_hmac('sha256', $payload, $webhookSecret);

if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    echo json_encode(['error' => 'Invalid signature']);
    exit;
}

$event = json_decode($payload, true);
$eventType = $event['event'];
$data = $event['data'];

switch ($eventType) {
    case 'package.delivered':
        // Update order status, notify customer, etc.
        break;
}

http_response_code(200);
echo json_encode(['received' => true]);

Retry and Failure Policy

Dropzon automatically retries failed webhook deliveries with exponential backoff. After persistent failures, webhooks are automatically disabled to protect both systems.

Max Attempts

3

Each event triggers up to 3 delivery attempts

Retry Delays

2s, 4s, 8s

Exponential backoff starting at 2 seconds

Request Timeout

10 seconds

Your endpoint must respond within 10 seconds

Auto-disable

10 failures

After 10 consecutive failures the webhook is auto-disabled

Re-enabling disabled webhooks: If your webhook is auto-disabled after 10 consecutive failures, fix your endpoint and then PATCH the webhook with { "active": true }. This resets the failure counter and resumes delivery.

Success Criteria

A delivery is considered successful when your endpoint returns any 2xx HTTP status code (200-299). Any other status code or a timeout triggers a retry.

ResponseResult
200-299Success - delivery marked as complete, failure counter reset to 0
3xx, 4xx, 5xxFailure - delivery retried (up to 3 attempts), failure counter incremented
Timeout (>10s)Failure - request aborted, delivery retried with exponential backoff
Connection errorFailure - DNS, TLS, or network errors trigger retry

Delivery History

Every delivery attempt is logged and accessible via the API. When you fetch a specific webhook with GET /api/v1/webhooks/:id, the response includes the last 25 delivery attempts with status codes, response bodies, timing, and error details.

// Example delivery record in the webhook detail response
{
  "deliveries": [
    {
      "id": "d1e2f3a4-b5c6-7890-abcd-ef1234567890",
      "event_type": "package.delivered",
      "status_code": 200,
      "success": true,
      "attempt": 1,
      "duration_ms": 145,
      "delivered_at": "2025-01-15T14:30:01.145Z",
      "error_message": null
    },
    {
      "id": "a9b8c7d6-e5f4-3210-abcd-ef0987654321",
      "event_type": "package.in_transit",
      "status_code": 500,
      "success": false,
      "attempt": 1,
      "duration_ms": 2340,
      "delivered_at": null,
      "error_message": null
    }
  ]
}

Quick Start

Register a webhook endpoint using your API key with the webhooks scope. See the Webhooks section in the API Reference above for full CRUD endpoint details.

# Register a webhook
curl -X POST https://api.dropzon.app/api/v1/webhooks \
  -H "Authorization: Bearer dzk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yoursite.com/webhooks/dropzon",
    "events": ["package.delivered", "package.in_transit", "package.collected"],
    "description": "Order status updates"
  }'

# Response includes the webhook secret - save it!
# {
#   "webhook": {
#     "id": "abc123...",
#     "url": "https://yoursite.com/webhooks/dropzon",
#     "events": ["package.delivered", "package.in_transit", "package.collected"],
#     "secret": "whsec_xxxxxxxxxxxxxxxx",
#     "active": true,
#     "description": "Order status updates"
#   }
# }

Save your webhook secret immediately. The secret is only returned in the registration response. You cannot retrieve it again later. If you lose it, delete and re-create the webhook.

Best Practices

Respond quickly

Return a 200 OK as soon as you receive and validate the webhook. Process the event asynchronously (e.g. add to a queue) to avoid hitting the 10-second timeout.

🔒

Always verify signatures

Never skip signature verification, even in development. Use constant-time comparison (timingSafeEqual, hmac.compare_digest, hash_equals) to prevent timing attacks.

🔁

Handle duplicates

Use the payload id field as an idempotency key. In rare cases (retries, network issues), the same event may be delivered more than once. Store processed IDs and skip duplicates.

📊

Monitor delivery history

Periodically check GET /api/v1/webhooks/:id to review delivery success rates, error messages, and response times. Catch issues before you hit the auto-disable threshold.

🎯

Subscribe to specific events

Only subscribe to the events you need. This reduces unnecessary traffic and simplifies your handler logic. You can update subscribed events anytime via PATCH.

🛡️

Use HTTPS endpoints only

Webhook URLs must use HTTPS. This is enforced by the API - HTTP URLs will be rejected during registration.

Limits

LimitValue
Webhooks per API key20
Webhook URL length2,048 characters
Description length500 characters
Response body logged1 KB (truncated)
Required API key scope"webhooks"

Shipping Rate Calculator

Get instant shipping estimates between any two DZ addresses

Overview

The Shipping Rate Calculator lets you get instant price estimates for sending packages between any two points in the Dropzon network. Use it to display shipping costs at checkout, build pricing tables, or validate costs before creating shipments.

Rates are calculated using Dropzon's zone-based pricing engine, which considers the origin and destination locations, package weight, and optional pickup/delivery services. The API automatically determines the shipping scope (in-city, domestic, cross-border, or international) based on the locations provided.

Endpoint:POST /api/v1/rates/estimate
Auth:API Key (read scope)
Currency:TZS (default)

Quick Start

Get a rate estimate with a single API call. Pass origin and destination DZ codes plus the package weight.

curl -X POST https://api.dropzon.app/api/v1/rates/estimate \
  -H "Authorization: Bearer dzk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "origin": "DZ#001-01",
    "destination": "DZ#002-05",
    "weight_kg": 2.5
  }'

The response includes a full price breakdown, the shipping scope (automatically determined), and estimated transit time.

{
  "success": true,
  "data": {
    "origin": {
      "dropzon_id": "abc-123",
      "name": "Kariakoo Hub",
      "code": "DZ#001"
    },
    "destination": {
      "dropzon_id": "def-456",
      "name": "Mikocheni Point",
      "code": "DZ#002"
    },
    "scope": "in_city",
    "weight_kg": 2.5,
    "pricing": {
      "pickup_fee": 0,
      "shipping_fee": 5750,
      "delivery_fee": 0,
      "total": 5750,
      "currency": "TZS"
    },
    "estimated_transit": {
      "min_days": 0,
      "max_days": 1
    },
    "pickup_method": "self_drop",
    "delivery_method": "pickup"
  }
}

Shipping Scopes

The API automatically determines the shipping scope based on the origin and destination locations. Each scope has different base rates, per-kg rates, and estimated transit times.

ScopeDescription
in_citySame city - origin and destination Dropzons are in the same city
domesticDifferent cities within the same country
cross_borderBetween neighboring countries (e.g. Tanzania to Kenya)
internationalOverseas corridors (US, China, UAE, UK)

Price Breakdown

Every rate estimate returns a detailed breakdown of the total cost. The formula depends on the shipping scope and the zone-pair pricing configured for those locations.

total = pickup_fee + shipping_fee + delivery_fee

shipping_fee = max(min_fee, base_fee + rate_per_kg x weight_kg)

FieldDescription
pickup_feeFee for door pickup from sender's location (0 if self_drop)
shipping_feeZone-based Dropzon-to-Dropzon shipping cost based on weight and distance
delivery_feeFee for last-mile door delivery to recipient (0 if pickup)
totalSum of all three fees - the full cost to ship this package
currencyISO currency code for all amounts (default: TZS)

Pickup and Delivery Options

Customize the rate estimate by specifying how the sender gets the package to the origin Dropzon and how the recipient receives it at the destination.

Pickup Methods (sender side)

self_drop

Sender walks to their nearest Dropzon and drops off the parcel. No pickup fee.

door_pickup

A Dropper rider picks up the parcel from the sender's door and brings it to the origin Dropzon. Adds a pickup fee.

Delivery Methods (recipient side)

pickup

Recipient picks up the package from their Dropzon location using OTP verification. No delivery fee.

dropper

A Dropper rider delivers the package from the Dropzon to the recipient's door. Adds a delivery fee.

Location Input Formats

The origin and destination fields accept multiple formats. Use whichever is most convenient for your integration.

FormatExample
DZ# User CodeDZ#001-01
DZ# Short CodeDZ#001
Full DZ CodeDZ-001-001-001-001-001
Dropzon UUIDa1b2c3d4-e5f6-...

Code Examples

JavaScript / Fetch

const response = await fetch('https://api.dropzon.app/api/v1/rates/estimate', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer dzk_your_api_key',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    origin: 'DZ#001-01',
    destination: 'DZ#002-05',
    weight_kg: 3.0,
    delivery_method: 'dropper',  // include door delivery fee
  }),
});

const { data } = await response.json();
console.log(data.pricing.total, data.pricing.currency);
// 8750 TZS
console.log(data.estimated_transit);
// { min_days: 0, max_days: 1 }

Python / Requests

import requests

response = requests.post(
    'https://api.dropzon.app/api/v1/rates/estimate',
    headers={'Authorization': 'Bearer dzk_your_api_key'},
    json={
        'origin': 'DZ#001-01',
        'destination': 'DZ#005-12',
        'weight_kg': 5.0,
        'pickup_method': 'door_pickup',
        'delivery_method': 'dropper',
    },
)

data = response.json()['data']
print(f"Shipping: {data['pricing']['total']} {data['pricing']['currency']}")
print(f"Scope: {data['scope']}")
print(f"Transit: {data['estimated_transit']['min_days']}-{data['estimated_transit']['max_days']} days")

PHP / cURL

$ch = curl_init('https://api.dropzon.app/api/v1/rates/estimate');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer dzk_your_api_key',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'origin' => 'DZ#001-01',
        'destination' => 'DZ#002-05',
        'weight_kg' => 2.5,
    ]),
]);

$response = json_decode(curl_exec($ch), true);
curl_close($ch);

$pricing = $response['data']['pricing'];
echo "Total: " . $pricing['total'] . " " . $pricing['currency'];

Common Use Cases

🛒

Checkout Shipping Costs

Display accurate shipping costs at checkout. When a customer enters their DZ code, call the rate estimator with the warehouse origin and customer destination to show the price before they pay.

📊

Pricing Tables

Build dynamic pricing pages that show shipping costs for different zones and weight brackets. Use the /rates/scopes endpoint to get scope definitions and call /rates/estimate for specific prices.

🔄

Bulk Quote Comparison

Compare shipping costs across different origin Dropzons to find the cheapest or fastest route for your logistics workflow.

📱

Mobile Shipping Calculator

Let users estimate shipping costs in your mobile app before they commit to a shipment. The API supports all DZ code formats for easy input.

Limits and Notes

LimitValue
Max weight500 kg
Rate limit100 requests/minute (per API key)
Required scope"read" (default on all keys)
CurrencyTZS (rates reflect destination country)

Estimates are not binding quotes. Actual shipping costs are calculated at the time of shipment creation and may differ slightly if zone pricing has been updated between the estimate and the shipment. Always use the rate estimate for display purposes and confirm the final price at shipment creation.

Bulk Dispatch API

Programmatically create and manage parcel shipments

Overview

The Bulk Dispatch API lets e-commerce platforms, logistics companies, and enterprise shippers create package dispatches programmatically. Send a single parcel or batch up to 50 parcels in one API call. Each dispatch gets a tracking number, triggers webhooks, and enters the Dropzon logistics pipeline automatically.

Base:https://api.dropzon.app/api/v1/dispatch
Scope required:dispatch
Max batch:50 parcels

Setup

1

Get an API key

Go to the Developer Console and create an API key if you don't have one.

2

Enable the dispatch scope

When creating or editing your API key, add the "dispatch" scope. Without it, dispatch endpoints return 403.

3

Know your DZ codes

Every dispatch requires a sender DZ code (your warehouse or pickup location) and a recipient DZ code. Use the Addresses API to look up or validate codes.

Package Lifecycle

Every dispatched parcel follows the same lifecycle through the Dropzon network. You can track status changes via the Tracking API or receive real-time updates via Webhooks.

receivedat_originin_transitat_destinationready_for_pickupdeliveredcollected

Additional terminal statuses: returned, lost

Single Dispatch

Create one parcel dispatch per request. Perfect for on-demand orders.

JavaScript

const response = await fetch('https://api.dropzon.app/api/v1/dispatch', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer dzk_your_api_key',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    sender_dz_code: 'DZ#001-01',
    recipient_name: 'Amina Hassan',
    recipient_phone: '+255712345678',
    recipient_dz_code: 'DZ#002-05',
    weight_kg: 1.5,
    description: 'Electronics - phone case',
    client_reference: 'ORD-2026-0042',
  }),
});

const { data } = await response.json();
console.log(data.tracking_number);  // DZL-A1B2C3D4
console.log(data.pricing.total_price, data.pricing.currency);  // 5000 TZS

Python

import requests

resp = requests.post(
    'https://api.dropzon.app/api/v1/dispatch',
    headers={'Authorization': 'Bearer dzk_your_api_key'},
    json={
        'sender_dz_code': 'DZ#001-01',
        'recipient_name': 'Baraka Ngowi',
        'recipient_phone': '+255621000000',
        'recipient_dz_code': 'DZ#003-12',
        'weight_kg': 3.0,
        'client_reference': 'SHOP-9001',
    },
)

pkg = resp.json()['data']
print(f"Tracking: {pkg['tracking_number']}")
print(f"Price: {pkg['pricing']['total_price']} {pkg['pricing']['currency']}")

Bulk Dispatch

Send up to 50 parcels in a single request. Ideal for end-of-day batch processing or syncing orders from your e-commerce platform. Partial success is supported - each parcel is processed independently and errors don't block other items.

const response = await fetch('https://api.dropzon.app/api/v1/dispatch/bulk', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer dzk_your_api_key',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    parcels: [
      {
        sender_dz_code: 'DZ#001-01',
        recipient_name: 'Amina Hassan',
        recipient_phone: '+255712345678',
        recipient_dz_code: 'DZ#002-05',
        weight_kg: 1.5,
        client_reference: 'ORD-001',
      },
      {
        sender_dz_code: 'DZ#001-01',
        recipient_name: 'Baraka Ngowi',
        recipient_phone: '+255621000000',
        recipient_dz_code: 'DZ#003-12',
        weight_kg: 3.0,
        client_reference: 'ORD-002',
      },
    ],
  }),
});

const { data } = await response.json();
console.log(`Dispatched: ${data.dispatched}/${data.total}`);
console.log(`Grand total: ${data.grand_total} ${data.currency}`);

// Check for any failures
if (data.errors) {
  data.errors.forEach(err => {
    console.error(`Parcel ${err.index} (${err.client_reference}) failed: ${err.error}`);
  });
}

Client References

Pass your own order ID in the client_reference field. Dropzon stores it and returns it in dispatch responses and webhook payloads. This lets you map Dropzon tracking numbers back to your internal orders without maintaining a separate lookup table.

// Request
{
  "sender_dz_code": "DZ#001-01",
  "recipient_name": "Customer",
  "recipient_phone": "+255700000000",
  "recipient_dz_code": "DZ#005-03",
  "weight_kg": 2.0,
  "client_reference": "SHOPIFY-ORD-12345"
}

// Response
{
  "id": "pkg-uuid",
  "tracking_number": "DZL-A1B2C3D4",
  "client_reference": "SHOPIFY-ORD-12345",
  "status": "received",
  ...
}

Combine with Webhooks

For the best integration experience, use the Dispatch API together with Webhooks. Register a webhook for package status events, then dispatch parcels. You'll receive real-time updates as each parcel moves through the Dropzon network.

1

Register a webhook for package.in_transit, package.delivered, and package.collected events

2

Dispatch parcels via POST /dispatch or POST /dispatch/bulk

3

Receive webhook payloads at your URL as status changes happen

4

Update your order status in your system based on webhook events

Limits and Notes

LimitValue
Max parcels per bulk request50
Max weight per parcel500 kg
Min weight per parcel0.1 kg
Rate limit100 requests/minute (per API key)
Required scope"dispatch"
CurrencyTZS (Tanzania Shilling)
SenderAlways the API key owner

Pricing is final at dispatch time. The shipping price returned in the dispatch response is the actual charge, not an estimate. Zone-based pricing is applied based on the origin and destination Dropzons at the moment of creation.

Ready to integrate?

Get your API key and start building with the Dropzon address network. Free tier available for development and testing.