Skip to main content

Migrate v1 → v2

The v1 integration webhook is deprecated. It continues to accept requests until 30-June-2026; after that date v1 endpoints will stop processing leads and return errors. Migrate to v2 before then.

v2 adds:

  • HMAC-SHA256 request signing — the webhook_uuid alone is no longer a credential.
  • Idempotency keys for safe retries with 24-hour dedupe.
  • Async 202 Accepted processing with a stable event_id for correlation.
v1 sunset: 30-June-2026

After this date, requests to the v1 URL will fail. Dashboard configuration (assignee pool, lead stage, lead group, labels, task config) carries over automatically — the migration is a client-side change to URL, headers, and a couple of body field names.

Identify your version

The URL path tells you which version your integration is on.

VersionURL patternDistinguishing feature
v1https://prod-api.superfone.co.in/superfone/webhook/integration/<uuid>No version segment in the path
v2https://prod-api.superfone.co.in/superfone/v2/webhook/integration/<uuid>Path contains /v2/

If the URL you currently POST to does not contain /v2/, you are on v1 and must migrate.

What changes

Concernv1v2
URL/webhook/integration/:webhook_uuid/v2/webhook/integration/:webhook_uuid
AuthUUID alone acted as the credentialHMAC-SHA256 signature over <timestamp>.<body>.<idempotency-key>
Required headerscontent-type onlycontent-type, x-webhook-signature, x-webhook-timestamp, idempotency-key
Response200 OK with { "success": true, "lead_id": <number> }202 Accepted with { event_id, status, request_id } — async
IdempotencyNoneRequired idempotency-key; 24-hour dedupe window
Body field — phonephonecustomer_phone

Other body fields (first_name, last_name, business_name, address, city, source, custom fields, etc.) are unchanged. See Receive Lead Webhook for the complete v2 body schema.

Migration steps

Work through the phases in order. Steps 1–4 are zero-risk code prep you can do on a feature branch; step 5 is the cutover; steps 6–7 verify and clean up.

Phase 1 — Prep (no traffic changes)

  1. Get your v2 webhook URL and signing secret. Open the integration in the Superfone dashboard and copy both values. Treat the signing secret like a password: store it in a secret manager, never commit it to a repo, never ship it to a browser.
  2. Update the URL in your caller from /webhook/integration/<uuid> to /v2/webhook/integration/<uuid>. The <uuid> itself does not change.
  3. Rename body fields:
    • phonecustomer_phone
    • Leave every other field as-is — email, first_name, last_name, business_name, address, city, source, custom fields, etc. are unchanged.

Phase 2 — Add signing and headers

  1. Generate a per-request idempotency-key (UUIDv4 is a good default) and a Unix-seconds x-webhook-timestamp. Keep both — you'll need them for the signature and the headers.
  2. Serialize the body once to a string of bytes. Don't pretty-print, sort keys, or re-serialize after this point — the signature is computed over the exact bytes you send.
  3. Compute the signature as HMAC-SHA256(<timestamp>.<body>.<idempotency-key>) using your signing secret as the HMAC key, then hex-encode (lowercase). See Signing for language-specific code.
  4. Send all four required headers alongside the body:
    • content-type: application/json
    • x-webhook-signature: signature=<hex-digest>
    • x-webhook-timestamp: <unix-seconds>
    • idempotency-key: <uuid>

Phase 3 — Validate against v2 in a non-production environment

  1. Send a known-good payload to the v2 URL from a staging or test integration. Confirm the response is 202 Accepted with a non-empty event_id.
  2. Open the dashboard activity log for that integration and confirm the event appears with the same event_id, and that a lead was created or updated as expected.
  3. Exercise error paths: send a bad signature (expect 401), send a stale timestamp (expect 401), replay the same idempotency-key (expect status: "duplicate" with the original event_id). This catches signing bugs before they touch production.

Phase 4 — Cut over production traffic

  1. Switch the production caller to v2. A flag-gated cutover (e.g. 1% → 10% → 100% by environment or tenant) is the safest path; an instant flip is fine if your test coverage from Phase 3 is solid.
  2. Don't dual-write. Posting the same lead to both v1 and v2 will create duplicate processing on each side — they don't share idempotency. Switch each caller from v1 to v2 in a single change.
  3. Adjust your retry policy to retry only on 5xx and 429, with exponential backoff plus jitter, reusing the same idempotency-key across retries of the same delivery. See Idempotency and retries.
  4. Stop reading lead_id from the response. v2 returns 202 Accepted as soon as the payload is queued; the lead_id is no longer in the response body. If you need the lead ID, correlate by event_id in the dashboard activity log, or look up the lead by phone via the Customer Management API.

Phase 5 — Verify and clean up

  1. Watch the activity log and your error metrics for 24–48 hours after cutover. Healthy v2 traffic looks like 202 responses, matching events in the log, no 401/409 clusters.
  2. Decommission the v1 caller path — remove the old URL, the old payload shape, and any code that read success/lead_id from the v1 response.
  3. Rotate the signing secret from the dashboard if it was pasted anywhere insecure during development (logs, screenshots, test fixtures).

Before / after request

v1 — unsigned POST, sync 200:

curl -X POST "https://prod-api.superfone.co.in/superfone/webhook/integration/8f4e2a31-2b6d-4c3a-9f1e-7a0b6c2d4e5f" \
-H "content-type: application/json" \
-d '{"phone":"+918000000001","first_name":"Asha","email":"asha@example.com"}'

# 200 OK
# {"success": true, "lead_id": 1234567}

v2 — HMAC-signed POST, async 202:

WEBHOOK_UUID="8f4e2a31-2b6d-4c3a-9f1e-7a0b6c2d4e5f"
SIGNING_SECRET="your_signing_secret_here"
URL="https://prod-api.superfone.co.in/superfone/v2/webhook/integration/${WEBHOOK_UUID}"

BODY='{"customer_phone":"+918000000001","first_name":"Asha","email":"asha@example.com"}'
TIMESTAMP=$(date +%s)
IDEMPOTENCY_KEY=$(uuidgen | tr '[:upper:]' '[:lower:]')

SIGNING_STRING="${TIMESTAMP}.${BODY}.${IDEMPOTENCY_KEY}"
SIGNATURE=$(printf "%s" "$SIGNING_STRING" | openssl dgst -sha256 -hmac "$SIGNING_SECRET" -hex | awk '{print $2}')

curl -X POST "$URL" \
-H "content-type: application/json" \
-H "x-webhook-signature: signature=${SIGNATURE}" \
-H "x-webhook-timestamp: ${TIMESTAMP}" \
-H "idempotency-key: ${IDEMPOTENCY_KEY}" \
--data "$BODY"

# 202 Accepted
# {"event_id":"ev_01HXYZK7QF2A3B4C5D6E7F8G9H","status":"accepted","request_id":"req_a1b2c3d4e5f6"}

Language-specific signed examples (Node, TypeScript, Python) are in Receive Lead Webhook → Code Examples.

How to confirm the migration worked

  1. Send a test payload to the v2 URL and confirm the response is 202 Accepted with a non-empty event_id.
  2. Open the dashboard activity log and verify the event appears against your integration with the matching event_id.
  3. Check that the lead was created or updated as expected (round-robin assignment, configured stage/group/labels applied).
  4. After a few days of clean v2 traffic, retire the v1 caller path.

Common migration pitfalls

  • Re-serializing the body between signing and sending. The signature is computed over the exact body bytes sent on the wire. Serialize once, sign those bytes, send those bytes.
  • Clock drift. x-webhook-timestamp must be within ±5 minutes of Superfone's clock. NTP-sync the box that builds the request.
  • Reusing idempotency-key with a different body. This returns 409 Conflict. Generate a new key per logical delivery; reuse the same key only when retrying the same payload.
  • Treating 202 as a processing failure. v2 is async by design — 202 means "queued", not "lead created". Check the activity log for the outcome.