Skip to main content

Overview

Your API key identifies you. The request signature proves that an order request was created by someone holding your signing secret and that its fields were not changed on the way. C2C verifies the signature before it validates the request or runs any business logic. A request with a missing, malformed or incorrect signature is rejected with 401, and no order is created. The signature is a field named signature inside the JSON request body.
Your signing secret (sksec_...) is issued together with your API key and is shown only once. Each API key has its own signing secret. Sign a request with the secret that belongs to the API key in that request’s C2C-API-Key header.

Which endpoints require a signature

Signatures are required on requests that move money. Read-only requests are authenticated by your API key alone.

Quick reference

Signature generation algorithm

1

Collect the fields you will send

Take every top-level field of the request body except signature.
2

Drop what isn't signed

Remove fields whose value is null or an empty string "", and fields whose value is an object or an array.
3

Lowercase the keys

orderType → ordertype, merchantOrderRef → merchantorderref, callbackUrl → callbackurl.
4

Sort by key

Sort the fields by their lowercase key in ascending ASCII (byte) order.
5

Build the query string

Join the fields as key=value pairs with &. Values are used as-is: no URL encoding and no JSON quoting.
6

Append your signing secret

Concatenate the secret directly to the end of the query string, with nothing in between.
7

Hash

Compute the MD5 of the UTF-8 bytes of that string and write it as 32 lowercase hex characters.
8

Send it

Add the result to the body as "signature": "<hex>" and send the body.

Visual example

Value formatting rules

A value is signed exactly as it appears in the JSON you send. If you build the body from an object, sign the same object you serialize.
The number is signed as written in the body, so 1500 signs as 1500 and 1500.50 signs as 1500.50. Most JSON libraries write 1500.5 for a floating-point 1500.50. That’s fine as long as the value you sign comes from the same object you serialize. Don’t use exponent notation such as 1.5e3.
Include or omit optional fields as you like, but sign what you send. Omitting a field and sending it as null produce the same signature. Every top-level field you send is signed, including fields C2C doesn’t use, so don’t add fields after signing.
Keys are lowercased before sorting, so callbackUrl and CallbackURL both sign as callbackurl. A body that contains the same key twice in different cases (for example amount and Amount) is rejected with SIGNATURE_MALFORMED.
metadata is a string. If it contains JSON, sign the string exactly as you send it, for example metadata={"cart":"A1"}.

Worked examples

Use these inputs to check your implementation. All three use the example signing secret sksec_Q9mTz4Lk8XwR2bN6vH1cJ7pY3sF5gD0aE8uK2iOq (example only, not a real credential). The query strings for the second and third examples are:
These worked examples are checked by C2C’s automated test suite against the production verification code.

Code examples

Each example signs a payload and sends a deposit order. The helper signs the same object that is serialized into the body.
Python’s Decimal and floats: str(Decimal("1500.50")) is 1500.50, but requests can’t serialize a Decimal. Send amounts as int or float, or serialize the body yourself and sign the values as they appear in it.

How C2C validates a request

Order-creation requests pass through these checks in order. A request rejected at any step never reaches the next one.
C2C doesn’t apply request rate limits to the Merchant API.

Signature error responses

401 Unauthorized
C2C never returns the signature it expected, or any part of your secret. Quote the traceId to support if you need help investigating a rejection.

Replay protection

A signature has no timestamp, so the same body always produces the same signature. C2C uses your Idempotency-Key to tell retries from replays:
Always send a unique merchantOrderRef. It is part of the signature, so it makes every order’s signature unique, and C2C also refuses to reuse a merchantOrderRef that is already in progress or completed (409).
You don’t need to re-sign a retry: the body and the Idempotency-Key stay exactly the same. See Idempotency.

Common mistakes and troubleshooting

Debugging checklist

1

Reproduce the worked example

Run your signing code on the worked examples. The deposit must produce 56eff91b5ab0351471a2a8d457320230.
2

Print the query string

Log the string you hash, with the secret removed. Keys lowercase, sorted, key=value joined with &, no signature, no null or empty values.
3

Check the values

Numbers as written in the body, booleans as true/false, strings unescaped.
4

Check the secret

The secret starts with sksec_, belongs to the same API key as your C2C-API-Key header, and is appended directly to the query string.
5

Check the hash

MD5 over UTF-8 bytes, written as 32 hex characters.

Signing secret management

Never put the signing secret in browser JavaScript, mobile apps or anything a customer can download. Sign requests on your server only.
Use environment variables or a secret manager such as Azure Key Vault, AWS Secrets Manager or HashiCorp Vault. Never commit it to version control, and never log it or the full string you hash.
If you suspect the secret or API key has leaked, contact C2C support immediately to revoke the key and issue a new key and secret.
Call the API over HTTPS only. The signature protects the request fields from tampering, not from being read.

Migrating from header signatures

Breaking change. C2C no longer accepts the X-C2C-Timestamp and X-C2C-Signature request headers (HMAC-SHA256 over the raw body). Order requests signed only that way are rejected with 401 SIGNATURE_REQUIRED.
1

Replace the signing helper

Use one of the code examples. Your signing secret doesn’t change.
2

Move the signature into the body

Send "signature": "<md5 hex>" inside the JSON body. Stop sending X-C2C-Timestamp and X-C2C-Signature; they are ignored.
3

Stop signing GET requests

Order status and balance requests need only the API key.
4

Keep retries on the same Idempotency-Key

Retries no longer need a fresh signature: resend the same body with the same Idempotency-Key.
5

Test against the worked examples

Your code must reproduce the worked examples before you deploy.
Webhooks that C2C sends you are not affected: they are still signed with HMAC-SHA256 in the X-C2C-Signature header.

Next steps

Create a deposit

Send your first signed request

Authentication

API keys, required headers and auth errors