Payment Webhook Handler
Every time there is a successful payment for your store, Teya makes a call to the webhook URLs created for that store.
The webhook request includes an x-teya-signature header that must be verified to ensure the call was made by Teya.
The public key for the signature was returned at the webhook creation from the Teya Business Portal. The public key associated to a specific webhook can be retrieved at any time from the Teya Business Portal.
We use the SHA256withRSA algorithm.
For the signature verification, you should use the entire payload received in the call made from Teya.
Webhook Payload Fields
| Field | Description | Example |
|---|---|---|
event |
Event type name | payment.succeeded.v1 |
timestamp |
Time the event occurred (ISO 8601) | 2025-10-01T08:36:06Z |
data.transaction_id |
Unique identifier for the transaction, which can be used for refunds and receipt creation. | tr_01999eea-03f5-78ae-962b-97ff946aad4d |
data.authentication_id |
Unique identifier for the authentication | ee8125ef-501f-4051-be2f-6714565aa0f1 |
data.payment_method.type |
Payment method type | CARD |
data.payment_method.brand |
Card brand | MASTERCARD |
data.payment_method.last4 |
Last 4 digits of card number | 0000 |
data.amount |
Payment amount, same as used in the session, payment link or transaction creation | {"value": 50, "currency": "GBP"} |
data.status |
Payment status, we only send for successful payments currently | SUCCESS |
data.session_id |
The payment session identifier, if applicable, can be used to identify the order. | 01999ee9-c5d6-791f-ba20-4156434915f6 |
data.payment_link_id |
The payment link identifier, if applicable. | 20eb276c-5b6b-4474-a5a1-49bcd6193148 |
data.authorisation_code |
Card authorisation code | 471274 |
data.merchant_reference |
Reference used when creating the session. | my-order-123 |
data.token_response |
Optional. Contains details of the token created for the card. null when no token was created for the payment. token_type is CARD_TOKEN for a token created from card details and WALLET_TOKEN for one created from Apple Pay; send it back when charging the token. |
{"token_id": "5f7b1a3a-3b7a-4b1a-9b1a-3a3b7a4b1a9b", "token_status": "SUCCESS", "token_type": "CARD_TOKEN"} |
Example Payload
Headers
user-agent: Teya-Webhooks
x-teya-signature: gOzDXhS7M8afuYjYx9EGTjOUXraexOijxN7V9f+hlourPwrZ6e6nrrs6mRlFb5xLyfCDIOneKUORdNNX9tFMPdTruPwLnjE6OhZnv9IKSJZ4B1C0aewED2koC4taO9Ye9sHqmK3ZRSP9c31CG2KCV3+mGa92UeDqfWtc+w6R8z4mr98yuvi76nHFjdTG59UIRgAjItr9vwo7HLbshMH0xe1MTmuaDx3D5pDJqmQAE0/kMptpbURwLU6oegaaX0CZ/4MB5A5Wq+PgdDxUxJ6A5N1s1bqtn7hs9nIgSfSvTCIKpOXjZeMQtw+8zT9CEEJ7ASwMAEk0LlN7v+2thutMqA==
Body
{
"event": "payment.succeeded.v1",
"timestamp": "2025-12-02T12:04:22Z",
"data": {
"transaction_id": "tr_019adef3-0d07-7543-9b74-efa2d5b6fa2a",
"payment_method": {
"type": "CARD",
"brand": "MASTERCARD",
"last4": "4065"
},
"amount": {
"value": 3434,
"currency": "GBP"
},
"status": "SUCCESS",
"session_id": "019adef2-e5b3-7e00-9fe1-61a5a01f3eef",
"payment_link_id": "019adef2-e00a-75f2-8d6d-490b345981b2",
"authentication_id": "019adef3-0cc3-7645-9d87-5cd0357e8509",
"authorisation_code": "776761",
"merchant_reference": "asdasd",
"token_response": null
}
}
Validate Signatures
Every webhook sent by Teya includes an x-teya-signature header containing a cryptographic signature. Validating this signature ensures:
- Authenticity: The webhook was genuinely sent by Teya
- Integrity: The payload has not been modified in transit
- Security: Protection against replay attacks and forged requests
Signature Algorithm
Teya uses SHA256withRSA (RSASSA-PKCS1-v1_5 with SHA-256) for webhook signatures. The signature is Base64 encoded.
Validation Process
Step 1: Extract the Signature
Extract the x-teya-signature header from the incoming webhook request:
x-teya-signature: gOzDXhS7M8afuYjYx9EGTjOUXra...
Step 2: Get the Raw Request Body
Use the entire raw request body exactly as received. Do not parse, format, or modify the JSON in any way.
Important: Use the raw bytes of the request body, not a parsed and re-serialized JSON object. Any modification to whitespace or field ordering will cause validation to fail.
Step 3: Retrieve Your Public Key
The public key for your webhook was provided when you created the webhook in the Teya Business Portal. You can retrieve it at any time from the portal under your webhook configuration.
Step 4: Verify the Signature
Decode the Base64 signature and verify it against the raw payload using your public key.
Delivery and Retries
Teya sends each event as an HTTP POST request to your webhook URL. Your endpoint must reply with a 2xx status code within 30 seconds to confirm it received the event. Teya also gives up on connecting to your server after 5 seconds.
When a delivery fails
Teya treats a delivery as failed when:
- Your endpoint returns any status code that is not
2xx(for example4xxor5xx) - Your endpoint does not reply within 30 seconds
- Teya cannot connect to your server (for example DNS failure, connection refused or TLS error)
Retry schedule
When a delivery fails, Teya retries it with increasing delays. Each event is attempted up to 6 times in total:
| Attempt | Delay after previous attempt | Time after event |
|---|---|---|
| 1st | Immediately | 00:00 |
| 2nd | 5 minutes | 00:05 |
| 3rd | 15 minutes | 00:20 |
| 4th | 1 hour | 01:20 |
| 5th | 2 hours | 03:20 |
| 6th | 6 hours | 09:20 |
If the 6th attempt also fails, Teya stops retrying and the event is not delivered. If you deactivate a webhook, any pending retries for it are dropped.
data.transaction_id to detect and ignore events you have already processed.
Tips for reliable delivery
- Reply first, process later: return
2xxas soon as you have stored the event, then do any slow work (sending emails, updating stock) asynchronously - Do not reply
2xxwhen you could not store the event: return a5xxinstead so Teya retries it - Keep your endpoint idempotent: processing the same event twice must not create duplicate orders or refunds
Troubleshooting
Common Issues
| Issue | Cause | Solution |
|---|---|---|
| Signature always fails | Modified payload | Use the raw request body, not parsed JSON |
| Invalid key format | Wrong key format | Ensure key is in PEM format with correct headers |
| Encoding errors | Character encoding | Use UTF-8 encoding consistently |
| Missing signature header | Framework stripping headers | Check your web framework's header handling |
Debugging Tips
- Log the raw payload: Print the exact bytes received to verify no modification occurred
- Check header case: Some frameworks lowercase headers (
x-teya-signaturevsX-Teya-Signature) - Verify key format: Ensure newlines are preserved in the PEM key
Security Best Practices
- Always validate signatures before processing webhook data
- Store keys securely using environment variables or a secrets manager
- Return quickly with a
2xxresponse to avoid webhook retries (see Delivery and Retries), then process asynchronously - Log validation failures for security monitoring