2. Guides
Migrating to TTDbooking
If you already integrate the Stays API, moving to TTDbooking is a host and credential swap. The contract is unchanged: same paths, same methods, same JSON bodies, same field names, same enums, same error codes, same booking flow.
Most partners finish in under an hour.
Your request and response bodies do not change. Not one field is renamed, added or removed. If your integration is generated from our schemas, regenerating is optional — your existing models still deserialize correctly.
TL;DR — what you change
| # | Change | Effort |
|---|---|---|
| 1 | Point your base URL at https://api.ttdbooking.com (production) or https://api-test.ttdbooking.com (test) |
Config |
| 2 | Swap in your new appKey / appSecret. Your old credentials will not work against TTDbooking |
Config |
| 3 | Re-register your callback URL with us, and allowlist our new egress | Config |
| 4 | If you filter inbound webhooks by User-Agent, update the value — see Callback changes |
Code (small) |
| 5 | Optional but recommended: verify the new callback signature | Code |
Items 1–3 are configuration. Item 4 is the only change that can silently break you, and only if you allowlist on User-Agent. Item 5 is new capability, not a requirement.
Endpoint mapping
Only the host changes. Every path, method and body is identical.
| Endpoint | Old | New | Body unchanged? |
|---|---|---|---|
| Host (prod) | https://api.hotelbyte.com |
https://api.ttdbooking.com |
— |
| Host (test) | https://api-test.hotelbyte.com |
https://api-test.ttdbooking.com |
— |
| Ticket | POST /api/auth/ticket |
POST /api/auth/ticket |
✅ Yes |
| Destinations | POST /api/search/destinations |
POST /api/search/destinations |
✅ Yes |
| HotelsMetadata | POST /api/search/hotelsMetadata |
POST /api/search/hotelsMetadata |
✅ Yes |
| HotelStaticDetail | POST /api/search/hotelStaticDetail |
POST /api/search/hotelStaticDetail |
✅ Yes |
| HotelList | POST /api/search/hotelList |
POST /api/search/hotelList |
✅ Yes |
| HotelRates | POST /api/search/hotelRates |
POST /api/search/hotelRates |
✅ Yes |
| CheckAvail | POST /api/search/checkAvail |
POST /api/search/checkAvail |
✅ Yes |
| Book | POST /api/trade/book |
POST /api/trade/book |
✅ Yes |
| QueryOrders | POST /api/trade/queryOrders |
POST /api/trade/queryOrders |
✅ Yes |
| Cancel | POST /api/trade/cancel |
POST /api/trade/cancel |
✅ Yes |
Unchanged along with the paths:
- The
{ "code": 0, "msg": "success", "data": { … } }envelope on every response. - Every business error code, including
100001111(ARI changed).100001112(credit limit) remains reserved and not currently emitted — no code path returns it, so there is nothing to change on your side. - Request headers:
Authorization,Content-Type,Request-Id,Trace-Id,Session-Id,Language,Currency,Timeout-Milliseconds. - Response headers:
Request-Id,Trace-Id,Server-Cost-Milliseconds,Session-Id. - The booking funnel: HotelList → HotelRates → CheckAvail → Book → QueryOrders / Cancel.
HotelStaticDetailremains [DEVELOPMENT]. Keep usingHotelsMetadatain production.
Renamed values
No field, header or enum was renamed. Three values differ, and only one of them is on the wire:
| What | Old | New | Affects you if… |
|---|---|---|---|
Webhook User-Agent |
hotel-be-webhook/1.0 |
ttdbooking-webhook/1.0 |
…you allowlist or filter callbacks by User-Agent. Check this. |
| Demo credential | hotelbyte_api_demo |
ttdbooking_api_demo |
…you use the sandbox credential in test |
| Sample env var names | HOTELBYTE_* |
TTDBOOKING_* |
…never. These are names in our sample code, not part of the contract — call yours whatever you like |
Authentication
Same request, same response shape, new host and new secrets.
Before
curl -X POST https://api.hotelbyte.com/api/auth/ticket \
-H "Content-Type: application/json" \
-d '{"appKey":"<your-old-key>","appSecret":"<your-old-secret>","ttl":3600}'After
curl -X POST https://api.ttdbooking.com/api/auth/ticket \
-H "Content-Type: application/json" \
-d '{"appKey":"<your-ttd-key>","appSecret":"<your-ttd-secret>","ttl":3600}'Both return:
{ "code": 0, "msg": "success", "data": { "ticket": "<jwt>" } }Then send Authorization: Bearer <ticket> on every other call, exactly as before.
Keep your ticket caching. Tickets are meant to be reused — cache until roughly five minutes before expiresAt and refresh then. Note that expiry is absolute and fixed at issuance: it is not extended by API activity, so a busy integration still expires on schedule and must renew proactively. Requesting a fresh ticket per call is the single most common cause of hitting the rate limit during a migration.
Callback changes
Order status callbacks work the same way: you supply callbackUrl on the Book request, we POST order status changes to it, you return 2xx.
The payload is byte-for-byte identical:
{
"customerReferenceNo": "REF123456",
"hotelConfirmNo": "HCN789012",
"event": "order_paid",
"eventTime": 1704067200000
}Events are unchanged: order_created, order_paid, order_cancelled.
Three things to do:
1. Re-register your callback URL. We do not inherit your existing registration. Send us the URL, or set callbackUrl on each Book request.
2. Update any User-Agent allowlist. Our callbacks now identify as:
User-Agent: ttdbooking-webhook/1.0If your endpoint rejects unknown user agents, callbacks will silently fail until you add this. This is the one change that breaks quietly — please check it explicitly.
3. Verify the signature (recommended). This is new capability, not a breaking change. The previous integration sent callbacks unsigned. Your existing handler keeps working unchanged; verification is additive.
Status: callback signing is active in both the test and production environments. Deliveries are signed as soon as a webhook signing secret has been issued for your credential — ask us for one and you can exercise verification in test straight away. Until a secret is issued, your callbacks are delivered unsigned exactly as before, so nothing changes for you until you are ready.
Each signed callback carries:
X-TTD-Signature: t=1704067200,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
X-TTD-Event-Id: evt_01HQ8Z3K9M2N4P6R8T0V2X4Y6At— Unix seconds when we signed the request.v1— HMAC-SHA256 of"{t}.{rawBody}", keyed with your webhook signing secret, hex encoded.X-TTD-Event-Id— stable per delivery attempt of an event; use it for idempotency.
Verify against the raw request body, before any JSON parsing or re-serialization — re-encoding changes the bytes and the signature will not match.
const crypto = require("crypto");
function verifyTtdSignature(rawBody, headerValue, secret, toleranceSec = 300) {
const parts = Object.fromEntries(
headerValue.split(",").map((kv) => kv.split("=")),
);
const timestamp = Number(parts.t);
if (!timestamp || !parts.v1) return false;
// Reject replays outside the tolerance window.
if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSec) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
// Constant-time compare — a plain === leaks timing information.
const a = Buffer.from(expected, "hex");
const b = Buffer.from(parts.v1, "hex");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}import hashlib, hmac, time
def verify_ttd_signature(raw_body: bytes, header: str, secret: str, tolerance=300) -> bool:
parts = dict(kv.split("=", 1) for kv in header.split(","))
try:
timestamp = int(parts["t"])
received = parts["v1"]
except (KeyError, ValueError):
return False
if abs(time.time() - timestamp) > tolerance:
return False
expected = hmac.new(
secret.encode(),
f"{timestamp}.".encode() + raw_body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, received)Your signing secret is issued with your credentials and is not your appSecret. Rotate it independently.
Retries and idempotency are unchanged. Failed deliveries retry with exponential backoff, and duplicate deliveries remain possible — keep your handler idempotent, keyed on customerReferenceNo + event, or on X-TTD-Event-Id.
Cutover plan
1. Validate in test
Point a non-production build at https://api-test.ttdbooking.com with your TTD test credentials and confirm:
- Ticket issues successfully, and your client caches and reuses it.
- A full funnel completes: HotelList → HotelRates → CheckAvail → Book.
-
CheckAvailprice-change handling still works — exercise the recheck loop. -
Bookreturning status1(Confirming) or0(Unknown) is polled viaQueryOrders, not rejected. -
Cancelreturns the expectedserviceFeeandstatus. - Error paths still map correctly, especially
100001111(ARI changed). (100001112, credit limit, is reserved and not currently emitted — there is no code path to exercise.) - Callbacks reach your endpoint — with the new
User-Agent. - If you implemented it: signature verification passes, and a deliberately corrupted signature fails.
2. Re-certification
If you were already certified, you generally do not need a full re-run. We ask for a short confirmation pass covering the funnel and one cancellation. If your integration changed beyond the host and credentials, we will schedule the full certification cases.
3. Switch production
Deploy the new host and credentials together — they are a matched pair, and a new host with old credentials fails closed on 100000401.
Nothing about the switch is stateful on your side: there is no migration window, no data copy, and no in-flight state held in your client.
4. Rollback
Until the legacy host is switched off, rollback is reverting your configuration — put back the previous base URL and credentials and redeploy. There is no schema change to undo.
In-flight bookings: orders placed against the legacy host remain queryable and cancellable there for the whole dual-run window. Because order identifiers are yours (customerReferenceNo), an order created on one host is not automatically visible on the other. Let orders placed before your switch settle on the legacy host, or contact us to have them migrated. Plan your switch for a low-volume window and you will have very few of these.
Timeline
Both hosts run in parallel throughout the migration. You choose when to switch.
The legacy hosts will stop serving TTD partner traffic no sooner than 90 days after the switchover date is announced. The exact date is still to be confirmed — you will receive it in writing, and nothing is switched off before then.
You will get written notice with the confirmed date, and a reminder before it takes effect. Until then nothing is switched off.
FAQ
Do my request or response bodies change?
No. No field is renamed, added or removed. Enums, error codes and the { code, msg, data } envelope are identical. The only wire-level value that changes is the callback User-Agent.
Do I need to re-certify? Usually a short confirmation pass, not a full re-run. See Re-certification.
Will my in-flight bookings survive the switch? Orders live with the host they were created on. Both hosts stay live during the dual-run window, so nothing is lost — but query and cancel an order on the host that created it. See Rollback.
How do I get new credentials?
Email integrations@ttdbooking.com. You will receive a test appKey/appSecret first, then production credentials plus your webhook signing secret once your test pass is confirmed.
Can I run both integrations at once? Yes. Both hosts are live during the dual-run window. Run TTDbooking in test while production stays on the legacy host for as long as you need.
Do I have to implement signature verification? No. It is additive — if we have not issued you a signing secret, your callbacks arrive unsigned and your existing handler keeps working. Signing is active in test as well as production, so once you ask us for a secret you can verify end to end in test before you go live. We recommend doing exactly that.
My old credentials return 100000401 against the new host. Is that a bug?
No, that is expected. Credentials are not shared between the two platforms. Use the ones issued to you for TTDbooking.
Are rate limits the same?
Comparable in shape, but they are TTDbooking's own and no ceiling from your previous account carries over. Ceilings are per second, per endpoint, per client IP: 100/second on search and content, 50/second on QueryOrders, 10/second on the transactional trio (CheckAvail, Book, Cancel) and 5/second on ticket issuance. Responses now include X-RateLimit-Limit, -Remaining and -Reset, so you can measure your real headroom during the migration instead of estimating it. Send us your expected volumes and your egress IP addresses before cutover. See Rate limit.
Need help?
Email integrations@ttdbooking.com with your appKey (never your secret) and, where relevant, the Request-Id and Trace-Id of the call you are asking about. Those two headers let us find your exact request in seconds.

