PartnerLink lets your app open OilTrails directly to a partner-provided field destination. Your app or backend creates a signed Universal Link containing destination data. OilTrails verifies the payload, opens in focused PartnerLink mode, and shows the user the location, routing, weather, and well-data context available for that destination.
PartnerLink users do not need an OilTrails account or subscription to open partner-provided locations.
Canonical URL:
https://oiltrails.ca/partnerlink-open?payload=<base64url-json>&sig=<hmac>
partnerlink-open Universal Link.Before you start coding, OilTrails will provide or confirm:
pid.view or route.For production apps, prefer signing PartnerLink URLs on your backend and returning the finished URL to the iOS app. The sample Swift code signs locally for demonstration, but secrets embedded in mobile apps can be extracted.
| Field | Required | Type | Description |
|---|---|---|---|
v | Yes | Integer | Payload version. Must be 1. |
pid | Yes | String | OilTrails-issued partner ID. |
company | No | String | Partner or customer company name. |
name | Yes | String | Human-readable destination name. |
query | Conditional | String | LSD, NTS, UWI, address, site name, or searchable location text. |
lat | Conditional | Number | Latitude. Required if lon is provided. |
lon | Conditional | Number | Longitude. Required if lat is provided. |
extid | No | String | Partner-side ticket, job, work order, or record ID. |
mode | No | String | Requested launch mode, such as view or route. Must be enabled for the partner. |
ts | Yes | Integer | Unix timestamp in seconds. |
Provide either query or both lat and lon. You may provide both query and coordinates for the best user experience.
LSD-only payload:
{
"v": 1,
"pid": "truck_co",
"company": "Truck Co",
"name": "Start LSD",
"query": "05-21-042-08W4",
"extid": "ticket_12345",
"mode": "route",
"ts": 1770000000
}
Coordinate payload:
{
"v": 1,
"pid": "truck_co",
"company": "Truck Co",
"name": "Battery Site 14",
"lat": 52.432473,
"lon": -114.375374,
"extid": "dispatch_8842",
"mode": "route",
"ts": 1770000000
}
Combined payload:
{
"v": 1,
"pid": "truck_co",
"company": "Truck Co",
"name": "Battery Site 14",
"query": "05-21-042-08W4",
"lat": 52.432473,
"lon": -114.375374,
"extid": "work_order_9911",
"mode": "route",
"ts": 1770000000
}
The signature must be calculated as:
sig = lowercase_hex(HMAC_SHA256(payload, shared_secret))
Where:
payload is the Base64URL-encoded JSON string.The final URL must match this contract:
payload = base64url(json)
sig = lowercase_hex(HMAC_SHA256(payload, secret))
url = https://oiltrails.ca/partnerlink-open?payload=<payload>&sig=<sig>
This helper builds a PartnerLink URL from an encodable payload. It signs locally for demonstration only. For production integrations, use backend signing when possible.
import Foundation
import CryptoKit
import SwiftUI
import UIKit
struct PartnerLinkPayload: Codable {
let v: Int
let pid: String
let company: String?
let name: String
let query: String?
let lat: Double?
let lon: Double?
let extid: String?
let mode: String?
let ts: Int
}
enum PartnerLinkError: Error {
case invalidBaseURL
case invalidFinalURL
}
struct PartnerLinkBuilder {
let partnerSecret: String
let baseURL = URL(string: "https://oiltrails.ca/partnerlink-open")!
func makeURL(payload: PartnerLinkPayload) throws -> URL {
let encoder = JSONEncoder()
encoder.outputFormatting = [.sortedKeys]
let jsonData = try encoder.encode(payload)
let encodedPayload = base64URLEncode(jsonData)
let signature = hmacSHA256Hex(message: encodedPayload, secret: partnerSecret)
var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false)
components?.queryItems = [
URLQueryItem(name: "payload", value: encodedPayload),
URLQueryItem(name: "sig", value: signature)
]
guard let url = components?.url else {
throw PartnerLinkError.invalidFinalURL
}
return url
}
private func base64URLEncode(_ data: Data) -> String {
data.base64EncodedString()
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "=", with: "")
}
private func hmacSHA256Hex(message: String, secret: String) -> String {
let key = SymmetricKey(data: Data(secret.utf8))
let signature = HMAC<SHA256>.authenticationCode(
for: Data(message.utf8),
using: key
)
return signature.map { String(format: "%02x", $0) }.joined()
}
}
let payload = PartnerLinkPayload(
v: 1,
pid: "truck_co",
company: "Truck Co",
name: "Battery Site 14",
query: "05-21-042-08W4",
lat: 52.432473,
lon: -114.375374,
extid: "work_order_9911",
mode: "route",
ts: Int(Date().timeIntervalSince1970)
)
let builder = PartnerLinkBuilder(partnerSecret: "replace_with_partner_secret")
let partnerLinkURL = try builder.makeURL(payload: payload)
SwiftUI open example:
@Environment(\.openURL) private var openURL
openURL(partnerLinkURL) { accepted in
print("PartnerLink accepted: \(accepted)")
}
UIKit open example:
UIApplication.shared.open(partnerLinkURL)
For production integrations, OilTrails recommends signing PartnerLink URLs on your backend. This keeps the shared PartnerLink secret out of the iOS app while still allowing the app to open the finished URL.
POST /partnerlink-url
Input: destination fields from app
Backend:
validate user can access destination
build payload
set ts to now
base64url encode payload
sign encoded payload with partner secret
return final URL
App:
open returned URL
Example Node.js signing function:
import crypto from "node:crypto";
function base64url(input) {
return Buffer.from(JSON.stringify(input))
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/g, "");
}
function signPartnerLinkPayload(encodedPayload, secret) {
return crypto
.createHmac("sha256", secret)
.update(encodedPayload)
.digest("hex");
}
export function buildPartnerLinkURL(destination, partnerSecret) {
const payload = {
v: 1,
pid: "truck_co",
company: "Truck Co",
name: destination.name,
query: destination.query,
lat: destination.lat,
lon: destination.lon,
extid: destination.id,
mode: "route",
ts: Math.floor(Date.now() / 1000),
};
const encodedPayload = base64url(payload);
const sig = signPartnerLinkPayload(encodedPayload, partnerSecret);
return `https://oiltrails.ca/partnerlink-open?payload=${encodedPayload}&sig=${sig}`;
}
Place the PartnerLink action where the user already sees the field destination:
The iOS app should request or build the PartnerLink URL only when the user is ready to open the destination. That keeps ts fresh and avoids stale signatures.
OilTrails opens in focused PartnerLink mode. This means users can access the partner-provided destination and relevant location context without an OilTrails account or subscription. Some full OilTrails features may be unavailable during this mode so users stay focused on the task.
This is expected behavior:
Before launch, confirm:
v, pid, name, and ts.query or both lat and lon.ts is generated immediately before signing.= padding./partnerlink-open.mode values.| Error | Meaning | Fix |
|---|---|---|
missing_payload | URL has no payload. | Add payload query parameter. |
missing_signature | URL has no signature. | Add sig query parameter. |
invalid_payload_encoding | Payload is not valid Base64URL. | Check Base64URL conversion. |
invalid_payload_json | Decoded payload is not valid JSON. | Validate payload before encoding. |
unsupported_version | v is not 1. | Set "v": 1. |
unknown_partner | pid is not recognized. | Confirm partner ID. |
invalid_signature | Signature mismatch. | Sign encoded payload with correct secret. |
expired_request | Timestamp is stale. | Generate ts immediately before signing. |
disabled_partner | Partner account inactive. | Contact OilTrails. |
invalid_destination | No valid query or coordinate pair. | Send query or both lat and lon. |
unsupported_mode | Mode not enabled. | Use an allowed mode. |
Useful items to keep in your integration test notes:
Recommended test matrix:
When logging, never log the shared signing secret. It is safe to send OilTrails the payload JSON, encoded payload, signature, final URL, and error code when troubleshooting.
New integrations should use:
/partnerlink-open?payload=...&sig=...
Legacy /sitelink, /open-sitelink, and ?t=<token> flows may exist for compatibility but should not be used for new builds.
When asking OilTrails for integration support, include:
extid.